http协议叫做超文本传输协议,HTTP是一个基于TCP/IP通信协议来传递数据,本章讲解http的请求和http的响应报文解析
本小节主要讲解http的请求报文和响应报文
http请求主要有请求行,请求头部,空行,和请求数据组成
请求行主要有请求方法,url和协议版本组装但是,有严格的格式要求,下面就来具体的解析
请求方法主要有:GET方法, post方法,opions ,Put ,Head ,Delete ,Trace 但是这里我们主要讲解的是get方法,和post方法这两种常见的方法。
get方法:
get请求通常用于获取服务器资源,也可以提交数据,但是通过url的方式提交数据是极其不安全的,因为数据会直接让人可以看到。例如:Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30, 而且传输的的数据大小会受到限制,因此进行数据提交或者上传文件时,建议用post的请求
post方法:
post方法是GET方法的一个替代方法,它主要是向Web服务器提交表单数据,尤其是大批量的数据。post方法克服了GET方法的一些缺点。通过POST方法提交表单数据时,数据不是作为URL请求的一部分而是作为标准数据传送给Web服务器,这就克服了GET方法中的信息无法保密和数据量太小的缺点。因此,出于安全的考虑以及对用户隐私的尊重,通常表单提交时采用POST方法
URL就是统一资源定位器(UniformResourceLocator:URL)。通俗地说,它是用来指出某一项信息的所在位置及存取方式;更严格一点来说,URL就是在WWW上指明通讯协议以及定位来享用网络上各式各样的服务功能。也就是告诉服务器,我要从哪个位置获取你资源
http协议有以下版本:
具体各个版本有什么更新就不具体介绍了,此字段主要是想做版本的兼容
http的样例如下:
请求方法 url 协议版本
GET /test HTTP/2.0
请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,常见的有如下
Header | 解释 | 示例 |
---|---|---|
Accept | 指定客户端能够接收的内容类型 | Accept: text/plain,text/html,application/json |
Accept-Charset | 浏览器可以接受的字符编码集 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com |
实例:
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
总体实例:
POST /test HTTP/2.0
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
空行
请求的数据
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
响应行一般由协议版本、状态码及其描述组成 比如 HTTP/1.1 200 OK
消息报头用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据,也是键值对的形式存在,常见有以下:
总体实例:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
响应正文
/*************************************************************************
* @Descripttion:
* @Version: 0.0.1
* @Author: Xiaofang
* @Date: 2020-08-26 19:14:03
* @LastEditors: Xiaofang
* @LastEditTime: 2020-09-02 23:58:12
*************************************************************************/
#include
#include
#include
#include "tcp.h"
#if defined(LINUX)
#include
#include
#include
#include
#include
#include
#include
#elif defined(ARM)/*填写ARM相关的socket头文件*/
#endif
/**
* @name: create_socket
* @description: 创建socket
* @note
* @return 套接字
*/
int create_socket(void)
{
int mysfd = socket(AF_INET, SOCK_STREAM, 0);
if(mysfd < 0){
printf("create_socket error = %d\r\n", mysfd);
return ERROR;
}
return mysfd;
}
/**
* @name: create_connect
* @description: Tcp连接
* @note: 传入的必须为ip,没有做域名解析
* @param sockfd :网络套接字
* @param host :ip
* @param port :端口
* @param timeout :超时时间,按照秒进行计算
* @return -1:错误 -2超时 大于0正常
*/
int create_connect(int sockfd, char *host, int port, int stimeout)
{
struct sockaddr_in sAddr;
struct timeval timeout;
fd_set fdr, fdw;
int rc;
int flags;
// 设置非阻塞
flags = fcntl(sockfd, F_GETFL, 0);
if(flags < 0){
printf("Get flags error:%d\n", errno);
return ERROR;
}
flags |= O_NONBLOCK;
if(fcntl(sockfd, F_SETFL, flags) < 0){
printf("Set flags error:%d\n", errno);
return ERROR;
}
//建立连接
memset(&sAddr, 0, sizeof(struct sockaddr));
sAddr.sin_family = AF_INET;
sAddr.sin_port = htons(port);
sAddr.sin_addr.s_addr= inet_addr(host);
// 客户端连接,如果大于0表示已经连接成功了,此时不需要做超时判断
rc = connect(sockfd, (struct sockaddr *)&sAddr, sizeof(sAddr));
if(rc >= 0){
goto SUCCESS;
}else{
if(errno == EINPROGRESS)
{
FD_ZERO(&fdr);
FD_ZERO(&fdw);
FD_SET(sockfd, &fdr);
FD_SET(sockfd, &fdw);
timeout.tv_sec = stimeout; //通常设置为3
timeout.tv_usec = 0;
rc = select(sockfd+1, &fdr, &fdw, NULL, &timeout);
if(rc > 0)
{
//判断套接字是否可读
if(!FD_ISSET(sockfd, &fdr) && FD_ISSET(sockfd, &fdw)){
//printf("http connect ok\r\n");
goto SUCCESS;
}
else if(FD_ISSET(sockfd, &fdr) && FD_ISSET(sockfd, &fdw))
{
socklen_t len = sizeof(rc);
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &rc, &len) < 0){
printf("getsockopt error\r\n");
return ERROR;
}
if(rc == 0){
goto SUCCESS;
}
else{
printf("http timeout,rc:%d\r\n", rc);
return TIMEOUT;
}
}
}
else if(rc == 0){
//返回-2代表连接超时
printf("http connect timeout\r\n");
return TIMEOUT;
}
else{
printf("http select error\r\n");
return ERROR;
}
}
}
SUCCESS:
//设置套接字为阻塞
flags = fcntl(sockfd, F_GETFL, 0);
if(flags < 0){
printf("Get flags error:%d\n", errno);
return ERROR;
}
flags &= ~O_NONBLOCK;
if(fcntl(sockfd, F_SETFL, flags) < 0){
printf("Set flags error:%d\n", errno);
return ERROR;
}
return rc;
}
/**
* @name: socket_send
* @description: tcp支持发送超时
* @note:
* @param sockfd : socket 套接字
* @param data : 需要发送的数据
* @param len : 需要发送数据的长度
* @param s : 设置超时时间按秒
* @param us : 设置超时时间按微秒
* @return 发送了多少个字节
*/
int socket_send(int sockfd, char *data, int len, int s, int us)
{
//传参判断
int rc = 0;
fd_set write_fds;
struct timeval timeout;
timeout.tv_sec = s;
timeout.tv_usec = us;
FD_ZERO(&write_fds);
FD_SET(sockfd, &write_fds);
rc = select(sockfd+1, NULL, &write_fds, NULL, &timeout);
if(rc < 0){
printf("send select error %d\n", rc);
return ERROR;
}
if(rc == 0){
printf("send select timeout\n");
return TIMEOUT;
}
if(rc > 0)
{
if(FD_ISSET(sockfd, &write_fds))
{
rc = send(sockfd, data, len, 0);
if(rc < 0)
{
printf("socket send error %d\n", rc);
return ERROR;
}
}else{
printf("FD_ISSET error\n");
return ERROR;
}
}
return rc;
}
/**
* @name: socket_recv
* @description: tcp支持接收超时
* @note:
* @param sockfd : socket 套接字
* @param data : 接收数据的缓冲区
* @param len : 接收数据缓冲区的长度
* @param s : 设置超时时间按秒
* @param us : 设置超时时间按微秒
* @return {type}
*/
int socket_recv(int sockfd, char *response, int len ,int s, int us)
{
int rc = 0;
fd_set read_fds;
struct timeval timeout;
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
timeout.tv_sec = s;
timeout.tv_usec = us;
rc = select(sockfd+1, &read_fds, NULL, NULL, &timeout);
if(rc < 0){
printf("recv select error\n");
return ERROR;
}
if(rc == 0){
printf("recv select timeout\n");
return TIMEOUT;
}
if(rc > 0)
{
if(FD_ISSET(sockfd, &read_fds)){
rc = recv(sockfd, response, len, 0);
if(rc < 0){
printf("recv error %d\n",rc);
return ERROR;
}
}else{
printf("FD_ISSET error\n");
return ERROR;
}
}
return rc;
}
#ifndef __TCP_H__
#define __TCP_H__
/**************************Macro definition***********************************/
/*宏开关,如果是linux平台,定义linux,否则定义ARM*/
#define LINUX
//#define ARM
typedef enum
{
TIMEOUT = -2, //超时
ERROR = -1, //错误
NORMAL = 0 //正常
}HttpState_e;
/*************************extern declaration ********************************/
/**************************Global variable***********************************/
/**************************Function declaration******************************/
/**
* @name: create_socket
* @description: 创建socket
* @note
* @return 套接字
*/
int create_socket(void);
/**
* @name: create_connect
* @description: Tcp连接
* @note: 传入的必须为ip,没有做域名解析
* @param sockfd :网络套接字
* @param host :ip
* @param port :端口
* @param timeout :超时时间,按照秒进行计算
* @return -1:错误 -2超时 大于0正常
*/
int create_connect(int sockfd, char *host, int port, int stimeout);
/**
* @name: socket_send
* @description: tcp支持发送超时
* @note:
* @param sockfd : socket 套接字
* @param data : 需要发送的数据
* @param len : 需要发送数据的长度
* @param s : 设置超时时间按秒
* @param us : 设置超时时间按微秒
* @return 发送了多少个字节
*/
int socket_send(int sockfd, char *data, int len, int s, int us);
/**
* @name: socket_recv
* @description: tcp支持接收超时
* @note:
* @param sockfd : socket 套接字
* @param data : 接收数据的缓冲区
* @param len : 接收数据缓冲区的长度
* @param s : 设置超时时间按秒
* @param us : 设置超时时间按微秒
* @return {type}
*/
int socket_recv(int sockfd, char *response, int len ,int s, int us);
#endif
/*************************************************************************
* @Descripttion:
* @Version:
* @Author: Xiaofang
* @Date: 2020-09-02 23:51:06
* @LastEditors: Xiaofang
* @LastEditTime: 2020-09-03 00:00:58
*************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "http.h"
#include "tcp.h"
int makehttprotocol(HttpParam_t httpparam, char *httppack)
{
char header[MAX_SIZE_HEARER] = {0}; //需要异常判断大小
char startmsg[128] = {0};
char endmsg[128] = {0};
char request[256] = {0};
unsigned char http_boundary[64]={0};
int startboundarylen;
int endboundarylen;
int requelen;
int Content_Length;
int headerlen;
int tolsize;
if(httpparam.method == GET)
{
if(httpparam.url == NULL){
sprintf(httppack, "GET / %s\r\n", HTTP1_1);
}else{
sprintf(httppack, "GET %s %s\r\n", httpparam.url, HTTP1_1);
}
//请求头: HTTP_DEFAULT_HEADER
sprintf(httppack, "%s%s\r\n",httppack, HTTP_DEFAULT_HEADER);
headerlen = sprintf(httppack, "%sHost: %s:%d\r\n",httppack, httpparam.host, httpparam.port);
if(httpparam.content != NULL)
{
sprintf(httppack, "%sContent-Length: %ld\r\n\r\n", httppack, httpparam.picparm.picsize);
}else{
sprintf(httppack, "%s\r\n", httppack);
}
return httpparam.picparm.picsize+headerlen;
}
else if(httpparam.method == POST)
{
if(httpparam.ispostpic == 0) //不上传图片,只是简单的post数据
{
if(httpparam.url == NULL){
sprintf(httppack, "POST / %s\r\n", HTTP1_1);
}else{
sprintf(httppack, "POST %s %s\r\n", httpparam.url, HTTP1_1);
}
//请求头: HTTP_DEFAULT_HEADER
sprintf(httppack, "%s%s\r\n",httppack, HTTP_DEFAULT_HEADER);
headerlen = sprintf(httppack, "%sHost: %s:%d\r\n",httppack, httpparam.host, httpparam.port);
if(httpparam.content != NULL)
{
sprintf(httppack, "%sContent-Length: %ld\r\n\r\n", httppack, httpparam.picparm.picsize);
}else{
sprintf(httppack, "%s\r\n", httppack);
}
sprintf(httppack, "%s%s\r\n", httppack, httpparam.content);
return httpparam.picparm.picsize+headerlen;
}
else if(httpparam.ispostpic == 1) //上传图片
{
long long int timestamp;
struct timeval tv;
gettimeofday(&tv,NULL);
timestamp = (long long int)tv.tv_sec * 1000 + tv.tv_usec;
//1. 获取http_boundary
sprintf(http_boundary,"----------------------------%lld",timestamp);
//2. start boundary
startboundarylen = sprintf(startmsg, "--%s\r\n",http_boundary);
//3. end boundart
endboundarylen = sprintf(endmsg, "\r\n--%s--\r\n",http_boundary);
//4.
int requelen = sprintf(request, UPLOAD_REQUEST, httpparam.picparm.picname);
int Content_Length = requelen + startboundarylen + httpparam.picparm.picsize + endboundarylen;
int headerlen = sprintf(header, HTTP_POST_PIC_HEAD, httpparam.url, httpparam.host, http_boundary, Content_Length);
int tolsize = Content_Length + strlen(HTTP_POST_PIC_HEAD) + 256; //加256防止溢出
memcpy(httppack, header, headerlen);
memcpy(httppack+headerlen, startmsg, startboundarylen);
memcpy(httppack+headerlen+startboundarylen, request, requelen);
memcpy(httppack+headerlen+startboundarylen+requelen, httpparam.content, httpparam.picparm.picsize);
memcpy(httppack+headerlen+startboundarylen+requelen+httpparam.picparm.picsize, endmsg, endboundarylen);
return tolsize;
}
}
return 0;
}
/**
* @name: http_post
* @description: 实现http post和get的请求
* @note:
* @param httpparam:http参数结构体
* @param content:post的内容
* @param len:内容的长度
* @param response:获得的响应内容
* @param resplen:需要获取响应内容大小
* @return -1错误 -2超时
*/
int http(HttpParam_t httpparam, char *content, int len, char *response, int resplen)
{
int rc=0;
int httpfp=0;
char *httppack;
httppack = malloc(len+512); //假设http头长度有512,http上传的最终头
memset(httppack, 0, sizeof(len+512));
httpfp = create_socket();
if(httpfp < 0)
{
free(httppack);
return ERROR;
}
rc = create_connect(httpfp, httpparam.host, httpparam.port, CONNECTTIMEOUT);
if(rc < 0)
{
free(httppack);
return rc;
}
rc = makehttprotocol(httpparam, httppack);
if(rc < 0)
{
free(httppack);
return rc;
}
rc = socket_send(httpfp, httppack, rc, httpparam.timeouts, httpparam.timeoutus);
if(rc < 0)
{
free(httppack);
return rc;
}
//此处存在接收不到的情况,可以通过多线程解决,不断的接收数据
rc = socket_recv(httpfp, response, resplen , httpparam.timeouts, httpparam.timeoutus);
if(rc < 0)
{
free(httppack);
return rc;
}
printf("response = %s\n",response);
free(httppack);
return 0;
}
int http_responed(char *respsone)
{
if(respsone == NULL){
return -1;
}
//1. 判断返回值中是否有200
int code;
char *resp;
resp = strstr(respsone , "\r\n\r\n");
resp += 4;
//2. 再获取其返回值内容
//printf("%s\n", resp);
/*
cJSON *root = cJSON_Parse(resp);
if(root == NULL)
{
printf("http respsone is null\n");
}
cJSON *pos;
pos = cJSON_GetObjectItem(root, "code");
if(pos != NULL){
code = (pos->valueint);
}else{
cJSON_Delete(root);
return -1;
}
if(code == 0){
cJSON_Delete(root);
return -1;
}
cJSON_Delete(root);
*/
return 0;
}
int main()
{
HttpParam_t httpparam;
char responed[1024] = {0};
memset(httpparam.host, 0, sizeof(httpparam.host));
memset(httpparam.url, 0, sizeof(httpparam.url));
strcpy(httpparam.host, "172.16.62.147");
strcpy(httpparam.url, "/lift/api/uploadFaultPic?id=123123123123 "); //后面的id是我们项目所需要的参数
strcpy(httpparam.picparm.picname , "123.png");
httpparam.ispostpic = 1;
httpparam.port = 80;
httpparam.method = POST;
httpparam.timeouts = 1;
httpparam.timeoutus = 0;
FILE* fp = fopen("123.png", "rb+"); //打开要上传的图片
if (fp == NULL){
printf("open file fail!\r\n");
return -1;
}
fseek(fp, 0, SEEK_END);
httpparam.picparm.picsize = ftell(fp);
rewind(fp);
printf("get the size is: %ld\n", httpparam.picparm.picsize);
httpparam.content = malloc(httpparam.picparm.picsize);
memset(httpparam.content, 0, sizeof(httpparam.picparm.picsize));
fread(httpparam.content, httpparam.picparm.picsize, 1, fp);
int re = http(httpparam, httpparam.content, httpparam.picparm.picsize, responed, 1024);
free(httpparam.content);
//printf("%s\n",content);
}
/*
* @Descripttion:
* @Version:
* @Author: Xiaofang
* @Date: 2020-09-02 23:51:17
* @LastEditors: Xiaofang
* @LastEditTime: 2020-09-02 23:55:21
*/
#ifndef __HTTP_H__
#define __HTTP_H__
#define CONNECTTIMEOUT 3
#define MAX_SIZE_HEARER 2048
//http版本
#define HTTP1_1 "HTTP/1.1"
#define HTTP2_0 "HTTP/2.0"
//hear普通头
#define HTTP_DEFAULT_HEADER "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:63.0) Gecko/20100101 Firefox/63.0\r\n" \
"Connection: keep-alive\r\n" \
"Cache-Control: no-cache\r\n" \
"Content-Type: application/json\r\n" \
"Accept: */*"
//http上传文件所需要的头//"enctype: multipart/form-data\r\n"
#define HTTP_POST_PIC_HEAD "POST %s HTTP/1.1\r\n"\
"User-Agent: PostmanRuntime/7.24.1\r\n"\
"Accept: */*\r\n"\
"Host: %s\r\n"\
"Accept-Encoding: gzip, deflate, br\r\n" \
"Connection: keep-alive\r\n" \
"Content-Type: multipart/form-data; boundary=%s\r\n"\
"Content-Length: %d\r\n\r\n"\
#define UPLOAD_REQUEST "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" \
"Content-Type: image/png\r\n\r\n"
/**************************Type definition **********************************/
//请求方式
typedef enum {
GET,
POST
}RequestMethod_e;
typedef struct
{
char picname[32];
unsigned long picsize;
}PicParm_t;
typedef struct
{
char host[16];
char url[128]; //包含参数,参数从url中传入
int ispostpic; //是否需要上传图片
int port;
int method;
int timeouts;
int timeoutus;
char *content; //需要申请空间,存放数据
PicParm_t picparm; //图片参数
}HttpParam_t;
/**************************Function declaration******************************/
int makehttprotocol(HttpParam_t httpparam, char *httppack);
/**
* @name: http_post
* @description: 实现http post和get的请求
* @note:
* @param httpparam:http参数结构体
* @param content:post的内容
* @param len:内容的长度
* @param response:获得的响应内容
* @param resplen:需要获取响应内容大小
* @return -1错误 -2超时
*/
int http(HttpParam_t httpparam, char *content, int len, char *response, int resplen);
#endif