最近在C语言项目开发中,需要用到了http上传文件,又不想用curl库,于是打算基于socket用C语言实现,也正好研究一波表单上传形式。
1、首先写一个上传文件html文件: file.html
<!DOCTYPE>
<html>
<head>
<meta charset="utf-8">
<title>File upload</title>
</head>
<body>
<div>
<form role="form" action="http://test.inteink.com/Server/interface_picture_upload.action?appid=wmj_BZ2WeU7K1iP&appsecret=MXkMcT43JsIgAwBwVJ30EbA2ovIcpfCQ&sn=T1868881608",
method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<button type="submit">Upload</button>
</form>
</div>
</body>
</html>
2、用火狐浏览器打开这个文件,选择一张图片点击传,并抓包, 如下所示。
OK,把头信息照抄一遍,头里面有些信息可以不要,也能上传成功,自己慢慢去尝试。
然后,再看一下参数项的请求负载。
简单分析下,发现请求负载中出现了几次 -----------------------------5907704431277628128378170588--,查询相关资料得知这是表单分隔符,可以自己定义。然后发现中间出现了一大串乱码,毫无疑问,这一定就是我们上传的文件内容了。按照这个思路,再计算一下Content-Length 的大小,定义一个足够大得buffer,将文件数据按这种形式放入buffer,写入套接字中即可。
因为http一般都会有回复信息,所以将数据写入完成后,立即读取套接字,将回复的信息保存至buffer中进行分析。
如下为自己写的代码以供大家参考:
http_post.c
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/stat.h>
#include "post.h"
unsigned char header[1024]={0};
unsigned char send_request[1024]={0};
unsigned char send_end[1024]={0};
unsigned char http_boundary[64]={0};
unsigned long get_file_size(const char *path) //获取文件大小
{
unsigned long filesize = -1;
struct stat statbuff;
if(stat(path, &statbuff) < 0){
return filesize;
}else{
filesize = statbuff.st_size;
}
return filesize;
}
int http_post_upload_pic(const unsigned char *IP, const unsigned int port,char *URL, const char *filepath,
char *ack_json, int ack_len) //Post方式上传图片
{
int cfd = -1;
int recbytes = -1;
int sin_size = -1;
char buffer[1024*10]={0};
struct sockaddr_in s_add,c_add;
cfd = socket(AF_INET, SOCK_STREAM, 0); //创建socket套接字
if(-1 == cfd)
{
printf("socket fail ! \r\n");
return -1;
}
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET; //IPV4
s_add.sin_addr.s_addr= inet_addr(IP);
s_add.sin_port=htons(port);
printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port);
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr))) //建立TCP连接
{
printf("connect fail !\r\n");
return -1;
}
//获取毫秒级的时间戳用于boundary的值
long long int timestamp;
struct timeval tv;
gettimeofday(&tv,NULL);
timestamp = (long long int)tv.tv_sec * 1000 + tv.tv_usec;
snprintf(http_boundary,64,"---------------------------%lld",timestamp);
unsigned long totalsize = 0;
unsigned long filesize = get_file_size(filepath); //文件大小
unsigned long request_len = snprintf(send_request,1024,UPLOAD_REQUEST,http_boundary,filepath); //请求信息
unsigned long end_len = snprintf(send_end,1024,"\r\n--%s--\r\n",http_boundary); //结束信息
totalsize = filesize + request_len + end_len;
unsigned long head_len = snprintf(header,1024,HTTP_HEAD,SERVER_PATH,URL,http_boundary,totalsize); //头信息
totalsize += head_len;
char* request = (char*)malloc(totalsize); //申请内存用于存放要发送的数据
if (request == NULL){
printf("malloc request fail !\r\n");
return -1;
}
request[0] = '\0';
/******* 拼接http字节流信息 *********/
strcat(request,header); //http头信息
strcat(request,send_request); //文件图片请求信息
FILE* fp = fopen(filepath, "rb+"); //打开要上传的图片
if (fp == NULL){
printf("open file fail!\r\n");
return -1;
}
int readbyte = fread(request+head_len+request_len, 1, filesize, fp);//读取上传的图片信息
if(readbyte < 1024) //小于1024个字节 则认为图片有问题
{
printf("Read picture data fail!\r\n");
return -1;
}
memcpy(request+head_len+request_len+filesize,send_end,end_len); //http结束信息
/********* 发送http 请求 ***********/
if(-1 == write(cfd,request,totalsize))
{
printf("send http package fail!\r\n");
return -1;
}
/********* 接受http post 回复的json信息 ***********/
if(-1 == (recbytes = read(cfd,buffer,10240)))
{
printf("read http ACK fail !\r\n");
return -1;
}
buffer[recbytes]='\0';
int index = 0,start_flag = 0;
int ack_json_len = 0;
for(index = 0; index<recbytes; index++)
{
if(buffer[index] == '{')
{
start_flag = 1;
}
if(start_flag)
ack_json[ack_json_len++] = buffer[index]; //遇到左大括号则开始拷贝
if(buffer[index] == '}') //遇到右大括号则停止拷贝
{
ack_json[ack_json_len] = '\0';
break;
}
}
if(ack_json_len > 0 && ack_json[ack_json_len-1] == '}') //遇到花括号且有json字符串
{
printf("Receive:%s\n",ack_json);
}
else
{
ack_json[0] = '\0';
printf("Receive http ACK fail!!\n");
printf("--- ack_json_len = %d\n",ack_json_len);
}
free(request);
fclose(fp);
close(cfd);
return 0;
}
int main(int argc, char *argv[])
{
int ack_len = 256;
char ack_json[256]={0};
int ret = http_post_upload_pic(SERVER_ADDR, SERVER_PORT,SERVER_URL,argv[1],ack_json,ack_len); //Post方式上传图片
if(ret == -1)
{
printf("\n\n----------- Post picture Fail!!\n");
}
return 0;
}
http_post.h
#ifndef __HTTP_POST__
#define __HTTP_POST__
#define SERVER_ADDR "47.97.190.1"
#define SERVER_PORT 80
#define SERVER_URL "test.inteink.com"
#define SERVER_PATH "/Server/interface_picture_upload.action?appid=wmj_BZ2WeU7K1iP&appsecret=MXkMcT43JsIgAwBwVJ30EbA2ovIcpfCQ&sn=T1868881608"
#define HTTP_HEAD "POST %s HTTP/1.1\r\n"\
"Host: %s\r\n"\
"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:59.0) Gecko/20100101 Firefox/59.0\r\n"\
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"\
"Accept-Language: en-US,en;q=0.5\r\n"\
"Accept-Encoding: gzip, deflate\r\n"\
"Content-Type: multipart/form-data; boundary=%s\r\n"\
"Content-Length: %ld\r\n"\
"Connection: close\r\n"\
"Upgrade-Insecure-Requests: 1\r\n"\
"DNT: 1\r\n\r\n"\
#define UPLOAD_REQUEST "--%s\r\n"\
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"\
"Content-Type: image/jpeg\r\n\r\n"
unsigned long get_file_size(const char *path);
int http_post_upload_pic(const unsigned char *IP, const unsigned int port,char *URL, const char *filepath,
char *ack_json, int ack_len); //Post方式上传图片
#endif