如何通过 C 中的 HTTP POST 请求发送图像或二进制数据

Posted

技术标签:

【中文标题】如何通过 C 中的 HTTP POST 请求发送图像或二进制数据【英文标题】:How to send image or binary data through HTTP POST request in C 【发布时间】:2022-01-23 17:53:07 【问题描述】:

我正在尝试使用用 C (Windows) 编写的客户端程序将二进制文件发布到 Web 服务器。我对套接字编程很陌生,所以尝试使用带有纯文本消息的multipart/form-data 和基于文本的文件(.txt、.html、.xml)的 POST 请求。这些似乎工作正常。但是在尝试发送 PNG 文件时,我遇到了一些问题。

以下是我读取二进制文件的方法

    FILE *file;
    char *fileName = "download.png";
    long int fileLength;
    
    //Open file, get its size
    file = fopen(fileName, "rb");
    fseek(file, 0, SEEK_END);
    fileLength = ftell(file);
    rewind(file);

    //Allocate buffer and read the file
    void *fileData = malloc(fileLength);
    memset(fileData, 0, fileLength);
    int n = fread(fileData, 1, fileLength, file);
    fclose(file);

我确认所有字节都被正确读取。

这就是我形成邮件标题和正文的方式

    //Prepare message body and header
    message_body = malloc((int)1000);
    sprintf(message_body, "--myboundary\r\n"
                          "Content-Type: application/octet-stream\r\n"
                          "Content-Disposition: form-data; name=\"myFile\"; filename=\"%s\"\r\n\r\n"
                          "%s\r\n--myboundary--", fileName, fileData);

    printf("\nSize of message_body is %d and message_body is \n%s\n", strlen(message_body), message_body);

    message_header = malloc((int)1024);
    sprintf(message_header, "POST %s HTTP/1.1\r\n"
                            "Host: %s\r\n"
                            "Content-Type: multipart/form-data; boundary=myboundary\r\n"
                            "Content-Length: %d\r\n\r\n", path, host, strlen(message_body));

    printf("Size of message_header is %d and message_header is \n%s\n", strlen(message_header), message_header);

连接和发送部分也可以正常工作,因为请求被正确接收。但是,收到的 png 文件格式不正确。 如果我在 printf 中使用 %s,终端会为 fileData 打印以下内容

ëPNG

我四处搜索,发现二进制数据的行为不像字符串,因此 printf/sprintf/strcat 等不能用于它们。由于二进制文件嵌入了空字符,%s 将无法正确打印。看起来这就是 fileData 只打印 PNG 标头的原因。

目前,我向服务器发送了两个 send() 请求。一个带有页眉,另一个带有正文和页脚。这适用于基于文本的文件。为避免将sprintf 用于二进制数据,我尝试发送一个请求标头、一个请求二进制数据(正文)和一个请求页脚。这似乎也不起作用。

另外,发现memcpy 可用于将二进制数据附加到普通字符串。那也没有用。这是我的尝试(不确定我的实现是否正确)。

    sprintf(message_body, "--myboundary\r\n"
                          "Content-Disposition: form-data; name=\"text1\"\r\n\r\n"
                          "text default\r\n"
                          "--myboundary\r\n"
                          "Content-Type: application/octet-stream\r\n"
                          "Content-Disposition: form-data; name=\"myFile\"; filename=\"%s\"\r\n\r\n", fileName);

    char *message_footer = "\r\n--myboundary--";

    char *message = (char *)malloc(strlen(message_body) + strlen(message_footer) + fileLength);
    
    strcat(message, message_body);
    memcpy(message, fileData, fileLength);
    memcpy(message, message_footer, strlen(message_footer));

我不知道如何发送需要附加字符串(标题)、二进制数据(有效负载)、字符串(页脚)的有效负载。

任何用于发送整个文件的建议/指针/参考链接将不胜感激。谢谢!

【问题讨论】:

您可以从 *** 上的 post 开始阅读 【参考方案1】:

如何打印二进制数据

在您的问题中,您说您无法使用printf 打印二进制数据,因为二进制数据包含值为0 的字节。另一个问题(你没有提到)是二进制数据可能包含不可打印的字符。

二进制数据通常以下列方式之一表示:

    十六进制表示 在文本表示中,用占位符替换不可打印的字符 以上两者

我建议您创建自己的简单函数来打印二进制数据,它实现了选项 #3。您可以使用函数isprint 来判断一个字符是否可打印,如果不是,您可以放置​​一些占位符(例如'X')。

这是一个小程序:

#include <stdio.h>
#include <ctype.h>
#include <string.h>

void print_binary( char *data, size_t length )

    for ( size_t i = 0; i < length; i += 16 )
    
        int bytes_in_line = length - i >= 16 ? 16 : length - i;

        //print line in hexadecimal representation
        for ( int j = 0; j < 16; j++ )
        
            if ( j < bytes_in_line )
                printf( "%02X ", data[i+j] );
            else
                printf( "   " );
        

        //add spacing between hexadecimal and textual representation
        printf( "  " );

        //print line in textual representation
        for ( int j = 0; j < 16; j++ )
        
            if ( j < bytes_in_line )
            
                if ( isprint( (unsigned char)data[i+j] ) )
                    putchar( data[i+j] );
                else
                    putchar( 'X' );
            
            else
            
                putchar( ' ' );
            
        

        putchar( '\n' );
    


int main( void )

    char *text = "This is a string with the unprintable backspace character \b.";
    print_binary( text, strlen( text ) );

    return 0;

这个程序的输出如下:

54 68 69 73 20 69 73 20 61 20 73 74 72 69 6E 67   This is a string
20 77 69 74 68 20 74 68 65 20 75 6E 70 72 69 6E    with the unprin
74 61 62 6C 65 20 62 61 63 6B 73 70 61 63 65 20   table backspace 
63 68 61 72 61 63 74 65 72 20 08 2E               character X.    

如您所见,函数 print_binary 以十六进制表示和文本表示形式打印数据,每行 16 个字节,并且在打印文本时,它正确地将不可打印的退格字符替换为占位符 'X' 字符表示。

printf 转换格式说明符错误

线

printf("\nSize of message_body is %d and message_body is \n%s\n", strlen(message_body), message_body);

错了。 strlen 的返回类型是 size_t,而不是 intsize_t 的正确 printf 转换格式说明符是 %zu,而不是 %d。使用错误的格式说明符会导致未定义的行为,这意味着它可能适用于某些平台,但不适用于其他平台。

用二进制数据连接字符串

以下几行是错误的:

    char *message = (char *)malloc(strlen(message_body) + strlen(message_footer) + fileLength);
    
    strcat(message, message_body);
    memcpy(message, fileData, fileLength);
    memcpy(message, message_footer, strlen(message_footer));

函数strcat 要求两个函数参数都指向以空值结尾的字符串。但是,不能保证第一个函数参数以空值结尾。我建议你使用strcpy 而不是strcat

另外,在您的问题中,您正确地指出文件二进制数据应附加到字符串中。但是,那不是行

memcpy(message, fileData, fileLength);

正在做。而是覆盖字符串。

为了将二进制数据附加到字符串,您应该只覆盖字符串的终止空字符,例如:

memcpy( message + strlen(message), fileData, fileLength );

【讨论】:

啊,我没有正确使用memcpy。这似乎成功了。感谢@Andreas Wenzel 花时间回答问题!

以上是关于如何通过 C 中的 HTTP POST 请求发送图像或二进制数据的主要内容,如果未能解决你的问题,请参考以下文章

如何通过远程方法发送 json-rpc http post 请求并在 java 中传递加密参数

如何在http post请求中发送参数以获取laravel中的数据

在 javascript 中的 HTTP POST 请求中发送 cookie

Objective-C如何使用标题中的嵌套字典将数据发送到Http Post请求

如何使用 VBA 从 Excel 向服务器发送 HTTP POST 请求?

如何通过 Windows 批处理脚本中的 curl 发送带有 json 正文的 POST 请求