HTTP协议
超文本传输协议,纯文本格式。
HTTP是基于TCP/IP协议来传递数据的。
HTTP协议工作于客户端-服务器端架构(C/S)上。浏览器作为HTTP客户端通过url向HTTP服务器端即WEB服务器发送请求
认识URL
Uniform Resourse Loctor 即统一资源定位符
URL就是我们平时所说的网址。
http协议默认端口号是80
https默认端口号是443
这里带层次的文件路径应该理解成一个相对路径,相对的是HTTP服务器的根目录,浏览器只能获取到这个根目录下的文件或数据,而不能访问其他目录下的资源。
https:相比http协议多了一层加密层ssl,功能是反运营商劫持。
当我们在在百度首页输入C++,再按下回车后,查询字符串变成了kd=c%2b%2b
,这是因为特殊字符被进行了转码操作。
认识urlencode 和urldecode
像 % ? / :等一些特殊字符,已经被url当作特殊字符处理了,当输入的查询串中有这样的特殊字符,就先对这些特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不⾜足4位直接处理),每2位做⼀一位,前⾯面加上%,编码成%XY格式
比如上面的C++,‘+’的ASCII码是43,对应的十六进制数就是2B,转码成c%2b%2b。
urldecode解码是urlencode的逆过程
url编码与解码工具
HTTP协议格式
1.HTTP请求格式
- 首行:[方法]+url+版本号
- header:请求的属性,冒号分割的键值对,每组属性之间以’\n’分割,遇到空行表示header结束
- body:空行后的内容都是body,GET请求body一般为空字符串;若body存在,header部分会有一个Content-Length属性来标记body的长度
当我们访问百度首页时:
常见的header有:
- Host:客户端告诉服务器请求url中的Web名称和端口号
- Connection:用来告诉服务器是否可以维持固定的HTTP连接。HTTP/1.1使用Keep-Alive为默认值
- User-Agent:用户操作系统和浏览器版本信息(可判定用户是手机还是PC端访问)
- Referere:当前页面是从哪个页面跳转过来的
- Location:搭配3XX状态码使用,当浏览器访问页面旧位置不存在时,重定向到该页面的新位置,告诉客户端接下来要去哪里访问
- Cookie:用于在客户端存储少量信息,服务器返回给客户端的用户信息,也可以用来实现会话功能。
通常保存身份信息,通过响应报文中的Set-Cookie字段,服务器返回的信息就会被浏览器保存下来,当浏览器再次访问同一个网站时就会自动在cookie中带上身份信息,多用于用户注册、登陆等。
字符串长度一般上限
- Content-Type:实体的数据类型(text/html等)
- Content-Length:body部分的长度
HTTP的方法也有多种:
- GET:通过请求url获得资源
若请求的资源是文本则原样返回,若是与业务相关的CGI程序,则执行程序后返回结果 - POST:传输实体主体,即向指定资源提交数据进行处理请求(例如提交表单或上传文件)
- PUT:传输文件
- DELETE:请求服务器删除指定页面
- OPTIONS:查询服务器支持那些方法
- HEAD:获得报文首部
- TRACE:回显服务器收到的请求,用于测试诊断
最常用的就是GET和POST方法
GET方法一般不带body,POST方法一般都带body
2.HTTP响应格式
- 首行:版本号 状态码 状态描述
- Header:响应的属性。冒号分隔的键值对,每组属性之间用’\n’分割,出现空行表示header部分结束
- Body:body中的内容就是HTTP服务器响应的结果,若返回了一个html页面,那么html页面内容就在body中
HTTP的状态码:
状态码是当客户端向服务器发送请求时,描述返回的请求结果。通过状态码,用户可以知道服务器端是正常处理了请求还是出现了错误。
状态码的数量多达60余种。
这里我们只介绍一些常见的状态码:
- 200 OK 请求已正常处理
- 204 No Content 请求已正常处理,但无资源可返回
- 206 Partial Content 表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求
- 301 Moved Permanently 永久性重定向
- 302 Found 临时性重定向
303 See Other 该状态码表示由于请求对应的资源存在着另一个 URI ,应使用 GET 方法定向获取请求的资源。和302功能相同,但必须使用GET方法获取资源。
当 301 、 302 、 303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET ,并删除请求报文内
的主体,之后请求会自动再次发送。400 Bad Request 表示请求报文中存在语法错误。
- 401 Unauthorized 该状态码表示发送的请求需要有通过 HTTP 认证
- 403 Forbidden 表示请求资源的访问被服务器拒绝了。(一般是访问权限的问题)
- 404 Not Found 服务器上无法找到请求的资源
- 500 Internal Server Error 服务器端在执行时法发生错误
- 503 Service Unavailable 服务器处于超负载或停机维护,无法处理请求
- 504 Bad Gateway 网关超时
GET /favicon.ico HTTP/1.1
这个HTTP请求首行中的favicon,ico一般是缩略的网站图标,它显示在浏览器的地址栏、浏览器标签上或者在收藏夹上,是展示网站个性的缩略logo标志,也可以说是网站头像,目前主要的浏览器都支持favicon.ico图标,如果要让网站看起来更专业、更美、更有个性,favicon.ico是必不可少的。
HTTP协议也存在一些缺点:
- 通信使用明文(不加密),内容可能会被窃听
- 不验证通信方身份,所以有可能遭遇伪装
- 无法证明报文的完整性,报文在收到时有可能已经被篡改
在所有未加密的协议中,这些问题都会存在
使用 HTTPS 通信机制可以有效地防止这些问题。
HTTP 协议中没有加密机制,但可以通过和 SSL ( Secure Socket Layer ,安
全套接层)或 TLS ( Transport Layer Security ,安全层传输协议)的组合使用,加密 HTTP 的通信内容。
用 SSL 建立安全通信线路之后,就可以在这条线路上进行 HTTP 通信了。与 SSL 组合使用的 HTTP 被称为 HTTPS ( HTTP Secure ,超文本传输安全协议)。
实现一个超简洁版的HTTP服务器
响应结果是自己手动构造好的”hello world”页面
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//初始化服务器
int ServerInit(const char* ip,short port)
{
int listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
perror("socket");
return -1;
}
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(ip);
addr.sin_port=htons(port);
int ret=bind(listen_sock,(sockaddr*)&addr,sizeof(addr));
if(ret<0)
{
perror("bind");
return -1;
}
ret=listen(listen_sock,5);
if(ret<0)
{
perror("listen");
return -1;
}
return listen_sock;
}
//线程入口函数
void* ThreadEntry(void *arg)
{
int64_t new_sock=(int64_t)arg;//64位机器上指针大小为8个字节,若转成int存在数据丢失
//1.读数据并解析,由于此处无脑返回helloworld,所以我们只读出数据不解析
char buf[1024*10]={0};
read(new_sock,buf,sizeof(buf)-1);
//2.根据请求计算相应,此处不需要进行计算
//3.把响应写回给客户端
const char* first_line="HTTP/1.1 200 OK\n";//首行
const char* blank_line="\n";//空行
const char* body="<h1>hello world</h1>";//body
char header[1024]={0};
sprintf(header,"Content_Length:%lu\n",strlen(body));//header
write(new_sock,first_line,strlen(first_line));
write(new_sock,header,strlen(header));
write(new_sock,blank_line,strlen(blank_line));
write(new_sock,body,strlen(body));
return NULL;
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("Usage: ./http_server.c [ip] [port]\n");
return 1;
}
//////////////////////////////////////////////////////////////
//http服务器往往是基于TCP来实现的
//1.创建一个TCP服务器
//2.按照http协议的格式解析请求,并按照http协议的格式构造响应
//////////////////////////////////////////////////////////////
int listen_sock=ServerInit(argv[1],atoi(argv[2]));
if(listen_sock<0)
{
printf("ServerInit failed!\n");
return 1;
}
printf("ServerInit OK!\n");
while(1)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int64_t new_sock=accept(listen_sock,(sockaddr*)&peer,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
pthread_t tid;
pthread_create(&tid,NULL,ThreadEntry,(void*)new_sock);
pthread_detach(tid);
}
close(listen_sock);
return 0;
}