Как отправить запрос на загрузку файла изображения на сервер 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)........для уведомления о строке

Другие вопросы по тегам