提问者:小点点

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


我正在尝试使用用C(Windows)编写的客户端程序将二进制文件POST到Web服务器。我对套接字编程很陌生,所以尝试使用纯文本消息和基于文本的文件(. txt、.html、.xml)的multipart/form-data进行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));

我被困在如何发送我的有效负载上,这需要附加字符串(标题)、二进制数据(有效负载)、字符串(页脚)。

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


共1个答案

匿名用户

在你的问题中,你说你在使用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("\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需要两个函数参数都指向以null结尾的字符串。但是,不能保证第一个函数参数是以null结尾的。我建议您使用strcpy而不是strcat

此外,在您的问题中,您正确地指出文件二进制数据应附加到字符串中。然而,这不是该行

memcpy(message, fileData, fileLength);

正在做。相反,它正在覆盖字符串。

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

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