Как отправить запрос на загрузку файла изображения на сервер LINE с multipart/form-data для публикации изображения в LINE Notify?
Я пытаюсь опубликовать изображение с локального источника с помощью LINE Notify, но получаю неверный запрос, как отправить правильный запрос с помощью HttpWebRequest с типом содержимого multipart/form-data в vb.net?
Я пробовал на CURL, и он проснулся:
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"
Вот что я сделал в 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://stackru.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
Нет проблем с отправкой только "сообщения", когда другие части, например, "imageFile", "stickerPackageId", "stickerId",... объединяются с, в результате получается ошибка (400). Однако, если имя поля меняется на маленькую букву, например, "stickerpackageid", "stickerid" vb не поймает никаких исключений, а просто отправит "сообщение" в Notify. Итак, я думаю, что неправильная часть должна быть в строке HTTP-запроса, или это нужно для преобразования каждого поля в двоичный массив? Если это так, как получить правильный результат?
2 ответа
POST multipart/form-data для уведомления строки, необходимо самостоятельно создать список выходных байтовых массивов. Ссылка на API LINE Notify https://notify-bot.line.me/doc/en/
Загрузить класс файла
public class FormFile
{
public string Name { get; set; }
public string ContentType { get; set; }
public string FilePath { get; set; }
public byte[] bytes { get; set; }
}
Данные испытаний
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#
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;
}
Я использую этот код, все в порядке, но
postData += Convert.ToBase64String(File.ReadAllBytes(txtPic.Text)) и vbCrLf слишком большой.....нужен формат ячейки UTF8 (двоичный jpg)........для уведомления о строке