I'm trying to post an image from local using LINE Notify, but get bad request, how to send the right request using HttpWebRequest with multipart/form-data content-type in vb.net?
I've tried on cURL, and it's woking:
curl -i -X POST https://notify-api.line.me/api/notify -H "Authorization: Bearer <TOKEN>" -F "message=test" -F "imageFile=@C:\PATH\to\file.jpg"
Here is what I've done in vb.net:
Imports System.IO
Imports System.Net
Imports System.Security.Cryptography
Imports System.Text
Public Class Form1
Private Sub btnPic_Click(sender As Object, e As EventArgs) Handles btnPic.Click
OpenFileDialog1.Filter = "Image File (*.jpg)|*.jpg;*.JPG |Image File (*.png)|*.png;*.PNG"
OpenFileDialog1.FileName = ""
If OpenFileDialog1.ShowDialog() <> DialogResult.Cancel Then
txtPic.Text = OpenFileDialog1.FileName
End If
End Sub
Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
' [references]
'https://notify-bot.line.me/doc/en/
'http://white5168.blogspot.com/2017/01/line-notify-6-line-notify.html
'https://aprico-media.com/posts/1824
'https://stackoverflow.com/questions/3890754/using-httpwebrequest-to-post-data-upload-image-using-multipart-form-data
Dim md5Hash As MD5 = MD5.Create()
'Boundary string
Dim strBound As String = "---------------------" & GetMd5Hash(md5Hash, CInt(Int((654321 * Rnd()) + 1))).Substring(0, 10)
Try
Dim request = DirectCast(WebRequest.Create("https://notify-api.line.me/api/notify"), HttpWebRequest)
Dim postData As String = "--" & strBound & vbCrLf
postData += "Content-Disposition: form-data; name=""message""" & vbCrLf & vbCrLf
postData += txtText.Text & vbCrLf
' [Not working] sticker part
'postData += "--" & strBound & vbCrLf
'postData += "Content-Disposition: form-data; name=""stickerPackageId""" & vbCrLf & vbCrLf
'postData += 1 & vbCrLf
'postData += "--" & strBound & vbCrLf
'postData += "Content-Disposition: form-data; name=""stickerId""" & vbCrLf & vbCrLf
'postData += 2 & vbCrLf
If txtPic.Text <> "" Then
Dim ext As String = Path.GetExtension(txtPic.Text)
If ext = ".jpg" Then
ext = "jpeg"
ElseIf ext = ".png" Then
ext = "png"
Else
MessageBox.Show("Sorry! LINE Notify supports only jpeg/png image file.")
btnPic.PerformClick()
Return
End If
' [Not working] image part
postData += "--" & strBound & vbCrLf
postData += "Content-Disposition: form-data; name=""imageFile""; filename=""" & Path.GetFileName(txtPic.Text) & """" & vbCrLf
postData += "Content-Type: image/" & ext & vbCrLf & vbCrLf
postData += Convert.ToBase64String(File.ReadAllBytes(txtPic.Text)) & vbCrLf
End If
postData += vbCrLf & "--" & strBound & "--"
Dim data = Encoding.UTF8.GetBytes(postData)
request.Method = "POST"
request.ContentType = "multipart/form-data; boundary=" & strBound
request.ContentLength = data.Length
request.Headers.Add("Authorization", "Bearer <TOKEN>")
Using stream = request.GetRequestStream()
stream.Write(data, 0, data.Length)
End Using
Dim response = DirectCast(request.GetResponse(), HttpWebResponse)
Dim responseString = New StreamReader(response.GetResponseStream()).ReadToEnd()
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1)
End Try
End Sub
Shared Function GetMd5Hash(ByVal md5Hash As MD5, ByVal Input As String) As String
' Convert the input string to a byte array and compute the hash.
Dim data As Byte() = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(Input))
' Create a new Stringbuilder to collect the bytes
' and create a string.
Dim sBuilder As New StringBuilder()
' Loop through each byte of the hashed data
' and format each one as a hexadecimal string.
Dim i As Integer
For i = 0 To data.Length - 1
sBuilder.Append(data(i).ToString("x2"))
Next i
' Return the hexadecimal string.
Return sBuilder.ToString()
End Function 'GetMd5Hash
End Class
There is no problem for sending only "message", whenever the other parts i.e."imageFile", "stickerPackageId", "stickerId",... are combined with, the result is getting error (400). However, if a field name is changed to small letter as i.e."stickerpackageid", "stickerid" vb will not catch any exception, but just send the "message" to Notify. So, I think a wrong part should be in http request string, or does it needed to convert each field into binary array? If that is the case how to obtain the right result?
POST multipart/form-data for line notify , need generate output byte array list by yourself LINE Notify API Reference https://notify-bot.line.me/doc/en/
Upload File Class
public class FormFile
{
public string Name { get; set; }
public string ContentType { get; set; }
public string FilePath { get; set; }
public byte[] bytes { get; set; }
}
Test Data
private void multipartTest(string access_token)
{
Dictionary<string, object> d = new Dictionary<string, object>()
{
// message , imageFile ... name is provided by LINE API
{ "message", @"message..." },
{ "imageFile", new FormFile(){ Name = "notify.jpg", ContentType = "image/jpeg", FilePath="notify.jpg" }
}
};
string boundary = "Boundary";
List<byte[]> output = genMultPart(d, boundary);
lineNotifyMultipart(access_token, boundary, output);
}
C# Code
private void lineNotifyMultipart(string access_token, string boundary, List<byte[]> output)
{
try
{
#region POST multipart/form-data
StringBuilder sb = new StringBuilder();
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(lineNotifyURL);
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=Boundary";
request.Timeout = 30000;
// header
sb.Clear();
sb.Append("Bearer ");
sb.Append(access_token);
request.Headers.Add("Authorization", sb.ToString());
// note: multipart/form-data boundary must exist in headers ContentType
sb.Clear();
sb.Append("multipart/form-data; boundary=");
sb.Append(boundary);
request.ContentType = sb.ToString();
// write Post Body Message
BinaryWriter bw = new BinaryWriter(request.GetRequestStream());
foreach(byte[] bytes in output)
bw.Write(bytes);
#endregion
getResponse(request);
}
catch (Exception ex)
{
#region Exception
StringBuilder sbEx = new StringBuilder();
sbEx.Append(ex.GetType());
sbEx.AppendLine();
sbEx.AppendLine(ex.Message);
sbEx.AppendLine(ex.StackTrace);
if (ex.InnerException != null)
sbEx.AppendLine(ex.InnerException.Message);
myException ex2 = new myException(sbEx.ToString());
//message(ex2.Message);
#endregion
}
}
private void getResponse(HttpWebRequest request)
{
StringBuilder sb = new StringBuilder();
string result = string.Empty;
StreamReader sr = null;
try
{
#region Get Response
if (request == null)
return;
// HttpWebRequest GetResponse() if error happened will trigger WebException
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
sb.AppendLine();
foreach (var x in response.Headers)
{
sb.Append(x);
sb.Append(" : ");
sb.Append(response.Headers[x.ToString()]);
if (x.ToString() == "X-RateLimit-Reset")
{
sb.Append(" ( ");
sb.Append(CheckFormat.ToEpcohDateTimeUTC(long.Parse(response.Headers[x.ToString()])));
sb.Append(" )");
}
sb.AppendLine();
}
using (sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sb.Append(result);
}
}
//message(sb.ToString());
#endregion
}
catch (WebException ex)
{
#region WebException handle
// WebException Response
using (HttpWebResponse response = (HttpWebResponse)ex.Response)
{
sb.AppendLine("Error");
foreach (var x in response.Headers)
{
sb.Append(x);
sb.Append(" : ");
sb.Append(response.Headers[x.ToString()]);
sb.AppendLine();
}
using (sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sb.Append(result);
}
//message(sb.ToString());
}
#endregion
}
}
public List<byte[]> genMultPart(Dictionary<string, object> parameters, string boundary)
{
StringBuilder sb = new StringBuilder();
sb.Clear();
sb.Append("\r\n--");
sb.Append(boundary);
sb.Append("\r\n");
string beginBoundary = sb.ToString();
sb.Clear();
sb.Append("\r\n--");
sb.Append(boundary);
sb.Append("--\r\n");
string endBoundary = sb.ToString();
sb.Clear();
sb.Append("Content-Type: multipart/form-data; boundary=");
sb.Append(boundary);
sb.Append("\r\n");
List<byte[]> byteList = new List<byte[]>();
byteList.Add(System.Text.Encoding.UTF8.GetBytes(sb.ToString()));
foreach (KeyValuePair<string, object> pair in parameters)
{
if (pair.Value is FormFile)
{
byteList.Add(System.Text.Encoding.ASCII.GetBytes(beginBoundary));
FormFile form = pair.Value as FormFile;
sb.Clear();
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append(pair.Key);
sb.Append("\"; filename=\"");
sb.Append(form.Name);
sb.Append("\"\r\nContent-Type: ");
sb.Append(form.ContentType);
sb.Append("\r\n\r\n");
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
byteList.Add(bytes);
if (form.bytes == null && !string.IsNullOrEmpty(form.FilePath))
{
FileStream fs = new FileStream(form.FilePath, FileMode.Open, FileAccess.Read);
MemoryStream ms = new MemoryStream();
fs.CopyTo(ms);
byteList.Add(ms.ToArray());
}
else
byteList.Add(form.bytes);
}
else
{
byteList.Add(System.Text.Encoding.ASCII.GetBytes(beginBoundary));
sb.Clear();
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append(pair.Key);
sb.Append("\"");
sb.Append("\r\n\r\n");
sb.Append(pair.Value);
string data = sb.ToString();
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(data);
byteList.Add(bytes);
}
}
byteList.Add(System.Text.Encoding.ASCII.GetBytes(endBoundary));
return byteList;
}