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

How to send image or binary data through HTTP POST request in C

我正在尝试使用用 C (Windows) 编写的客户端程序将二进制文件 POST 发送到 Web 服务器。我对套接字编程还很陌生,所以尝试了 POST 请求,使用 multipart/form-data 和纯文本消息,以及 text-based 文件(.txt、.html、.xml).那些似乎工作正常。但是在尝试发送 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);

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

这就是我形成消息的方式 header 和 body

    //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 文件是 ill-formatted。 如果我在 printf

中使用 %s,终端会为 fileData 打印出以下内容
ëPNG

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

的原因

目前,我向服务器发送了两个 send() 请求。一个带有 header,另一个带有 body 和页脚组合。这适用于 text-based 个文件。为避免将 sprintf 用于二进制数据,我尝试发送一个 header 请求,一个用于二进制数据 (body) 和一个用于页脚。这似乎也不起作用。

此外,发现 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));

我不知道如何发送需要附加字符串 (headers)、二进制数据 (payload)、字符串 (footer) 的负载。

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

如何打印二进制数据

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

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

  1. 十六进制表示
  2. 在文本表示中,将不可打印的字符替换为占位符字符
  3. 以上两者

我建议您创建自己的用于打印二进制数据的简单函数,它实现选项 #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 的 return 类型是 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 要求两个函数参数都指向以 null 结尾的字符串。但是,不保证第一个函数参数以 null 终止。我建议您使用 strcpy 而不是 strcat.

此外,在您的问题中,您正确地声明了文件二进制数据应该附加到字符串中。然而,那不是什么行

memcpy(message, fileData, fileLength);

正在做。它会覆盖字符串。

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

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