HTTP响应格式
HTTP请求格式
HTTP规定,HTTP请求由如下3部分组成:
- 请求方法、URI和HTTP版本;
- 请求头;
- 请求正文。
下面的代码是一个HTTP/1.1请求的例子。
GET /bird.gif HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: “Chromium”;v=“112”, “Google Chrome”;v=“112”, “Not:A-Brand”;v=“99”
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: “Windows”
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
1.请求方式、URI和HTTP的版本
HTTP请求的第一行包括请求方式、URI和协议版本3项内容,以空格分开:
GET /bird.gif HTTP/1.1
在以上代码中,“GET”为请求方式,“/bird.gif”为URI,“HTTP/1.1”为HTTP的版本。
根据HTTP协议,HTTP请求可以由多种方式,主要包括一下几种。
- GET:这种请求方式是最为常见的,客户程序通过这种方式访问服务器上的一个文档,服务器把文档发送给客户端。
- POST:客户程序可以通过这种方式发送大量信息给服务器。在HTTP请求中除了要访问的文档的URI,还包括大量的请求正文,这些请求正文中通常会包含HTML表单数据。
- HEAD:客户程序和服务器之间交流一下内部数据,服务器不会返回具体的文档。当使用GET和POST方法时,服务器最后都将特定的文档返回给客户程序。而HEAD请求方式则不同,它仅仅交流一些内部数据,这些数据不会影响用户浏览网页的过程,可以说对用户时透明的。HEAD请求方式通常不单独使用,而是对其他请求方式起辅助作用。一些搜索引擎使用HEAD请求方式来获得网页的标志信息,还有一些HTTP服务器进行安全认证时,用这个方式来传递认证信息。
- PUT:客户程序通过这种方式把文档上传给服务器。
- DELETE:客户程序通过这种方式来删除远程服务器上的某个文档。客户程序可以利用PUT和DELETE请求方式来管理远程服务器上的文档。
GET和POST请求方式最常用,而PUT和DELETE请求方式并不常用,因此不少HTTP服务器不支持PUT和DELETE请求方式。
统一资源定位符(Universal Resource Identifer,URI)用于标记要访问的网络资源。在HTTP请求中,通常只要给出相对于服务器根目录的相对目录即可,相对根目录以“/开头。
HTTP请求的第一行的最后一部分内容为客户程序使用的HTTP请求。
2.请求头(Request Header)
请求头包含许多有关客户端和请求正文的有用信息。例如,请求头可以声明浏览器的类型、所用语言、请求客户端系统平台等:
sec-ch-ua: “Chromium”;v=“112”, “Google Chrome”;v=“112”, “Not:A-Brand”;v=“99”
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua-platform: “Windows”
3.请求正文(Request Content)
HTTP规定和请求正文之间必须空行分隔(即只有CRLN符号的行),这个空行非常重要,它表示请求头已经结束,接下来时请求正文。请求正文中可以包含客户以POST方式提交的表单数据,这个数据和请求头之间隔一行。
HTTP响应格式
和HTTP请求相似,HTTP响应也由3部分组成,分别是:
- HTTP的版本、状态码和描述;
- 响应头(Response Header);
- 响应正文(Response Content)。
下面是一个HTTP/1.1响应的例子:
HTTP/1.1 200 ok
Content-Type:txt/html
<html>
<head>
<title>HelloWorld</title>
</head>
<body >
<h1>Hello</h1>
</body>
</html>
1.HTTP的版本、状态码和描述
HTTP响应的第一行包括服务器使用的HTTP的版本、状态代码,以及对状态码的描述,这3项内容之间以空格分隔。在本例中,使用HTTP/1.1协议,状态码为200,该状态代码表示服务器成功处理了客户端发出的请求:
HTTP/1.1 200 ok
状态码是一个3位整数,以1、2、3、4或5开头:
- 1xx:信息提示,表示临时的响应。
- 2xx:响应成功,表明服务器成功地接收了客户端请求。
- 3xx:重定向。
- 4xx:客户端错误,表明客户端可能有问题。
- 5xx:服务器错误,表明客户端可能有问题。
以下是一些常见的状态代码:
- 200:响应成功。
- 400:错误的请求。客户端发送的HTTP请求不正确。
- 404:文件不存在。在服务器上没有客户要访问的文档。
- 405:服务器不支持客户的请求方式。
- 500:服务器内部错误。
2.响应头(ResponseHeader)
响应头也和请求头一样包含许多有用的信息,例如服务器类型、正文类型和正文长度等,如下所示:
Server:Apache-Coyote/1.1
Content-Type:txt/html
Content-Length:102
3.响应正文(Response Content)
响应正文就是服务器返回的具体数据,它是浏览器正真请求访问的信息,最常见的是HTML文档,如下所示:
<html>
<head>
<title>HelloWorld</title>
</head>
<body >
<h1>Hello</h1>
</body>
</html>
HTTP请求头与请求正文之间必须用空行分隔,同样,HTTP响应头与响应正文之间也必须用空行分隔。
当浏览器接收到HTTP请求后,会根据相应正文的不同类型来进行不同的处理。例如,对于大多数浏览器,如果响应正文是DOC文档,就会借助安装在本地的Word程序来打开这份DOC文档,如果响应正文是RAR压缩文档,就会弹出一个下载窗口让用户下载。
正文部分的MIME类型
HTTP请求以及响应的正文部分可以是任意格式的数据,如何保证接收方能“看懂”发送方的正文数据呢?HTTP采用MIME协议来规范正文的数据格式。
MIME协议由W3C组织制定,RFC2045文档(https://www.ietf.org/rfc/rfc2045.txt)对MIME协议做了详细阐述。MIME(Multipurpose Internet Mail Extension)是指多用途网络邮件扩展协议,这里的邮件不单纯地指E-Mail,还可以包括各种应用层协议在网络上传输地数据。因此,也可以将HTTP中的请求正文和响应正文看作邮件。
遵从MIME协议地数据统称为MIME类型。在HTTP请求头和HTTP响应头中都有一个Content-type项,用来指定请求正文部分或响应正文部分地MIME类型。下表列出了常见的MIME类型与扩展名之间的对应关系。
文件扩展名 | MIME类型 |
---|---|
未知的数据类型或不可识别的扩展名 | content/unknown |
.bin、.exe、.o、.a、.z | application/octet-stream |
application/pdf | |
.zip | application/zip |
.tar | application/x-tar |
.gif | image/gif |
.jpg、.jpeg | image/jpeg |
.htm、.html | text/html |
.text、.c、.h、.txt、.java | text/plain |
.mpg、.mpeg | video/mpeg |
.xmll | application/xml |
代码样例
HttpClient代码
package client;
import java.net.*;
import java.io.*;
import java.util.*;
public class HttpClient {
public static void main(String args[])
{
//确定HTTP请求uri
String uri = "hello1.htm";
if(0 != args.length)uri = args[0];
//按照GET请求方式访问HTTPServer
doGet("localhost", 8080, uri);
}
//按照GET请求方式访问HTTPServer
public static void doGet(String host, int port, String uri)
{
Socket socket = null;
try {
socket = new Socket(host, port);
}catch(Exception e) {e.printStackTrace();}
try {
//创建HTTP请求
StringBuffer sb = new StringBuffer("GET "+uri+" http/1.1\r\n");
sb.append("Accept: */*\r\n");
sb.append("Accept-Language:zh-cn\r\n");
sb.append("Accept-Encoding:gzip,deflate\r\n");
sb.append("User-Agent:HTTPClient\r\n");
sb.append("Host:localhost:8080\r\n");
sb.append("Connection:keep-Alive\r\n\r\n");
//发送Http请求
//获取输出流
OutputStream socketOut = socket.getOutputStream();
socketOut.write(sb.toString().getBytes());
//睡眠2秒,等待响应结果
Thread.sleep(2000);
//接受响应结果
//获取输入流
InputStream socketIn = socket.getInputStream();
int size = socketIn.available();
byte[] buffer = new byte[size];
socketIn.read(buffer);
//打印响应结果
System.out.println(new String(buffer));
}catch(Exception e){e.printStackTrace();}
finally {
try {
socket.close();
}catch(Exception e) {e.printStackTrace();}
}
}
}
HttpServer代码
package server;
import java.io.*;
import java.net.*;
public class HttpServer {
public static void main(String args[])
{
int port = 0;
ServerSocket serverSocket;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e){
System.out.println("port= 8080 默认");
port = 8080;
}
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器监听端口:"+serverSocket.getLocalPort());
//服务器在一个无限循环钟不断接受来自客户端的TCP连接请求
while(true)
{
try {
//等待客户端的TCP连接请求
final Socket socket = serverSocket.accept();
System.out.println("建立了与客户的一个新的TCP连接,该客户的地址为:"+socket.getInetAddress()+":"+socket.getPort());
//响应客户请求
service(socket);
}catch(Exception e) {
System.out.println("客户端请求的资源不存在");
}
}
}catch(Exception e) {e.printStackTrace();}
}
//响应HTTP请求信息
public static void service(Socket socket)throws Exception{
//读取HTTP的请求信息,获取输入流
InputStream socketIn = socket.getInputStream();
Thread.sleep(500); //睡眠500毫秒,等待HTTP请求
int size = socketIn.available();
byte[] buffer = new byte[size];
socketIn.read(buffer);
String request = new String(buffer);
System.out.println(request); //打印HTTP请求数据内容
//解析HTTP请求
//获取HTTP请求的第一行
int endIndex = request.indexOf("\r\n");
if(-1 == endIndex)
endIndex = request.length();
String firstLineOfRequest =
request.substring(0, endIndex);
//解析HTTP请求的第一行
String[] parts = firstLineOfRequest.split(" ");
String uri = "";
if(2 <= parts.length)
uri = parts[1]; //获取HTTP请求中的uri
System.out.println("uri:"+uri);
//决定HTTP响应正文的类型,此处做了简化处理
String contentType;
if(-1 != uri.indexOf("html")|| -1 != uri.indexOf("htm"))
contentType = "txt/html";
else if(-1 != uri.indexOf("jpg") || -1 == uri.indexOf("jpeg"))
contentType = "image/jpeg";
else if(-1 != uri.indexOf("gif"))
contentType = "image/gif";
else
//字节流类型
contentType = "application/octet-string";
//创建HTTP响应结果
//HTTP响应第一行
String responseFirstLine = "HTTP/1.1 200 ok\r\n";
//HTTP响应头
String responseHeader = "Content-Type:"+contentType+"\r\n\r\n";
//获得读取响应正文数据的输入流
InputStream in;
if(0 < uri.length() && '/' == uri.charAt(0))
{
in = HttpServer.class.getResourceAsStream("root"+uri);
System.out.println("path:"+"root"+uri);
}
else
{
in = HttpServer.class.getResourceAsStream("root/"+uri);
System.out.println("path:"+"root/"+uri);
}
//发送HTTP响应结果
//获取输入流
OutputStream socketOut = socket.getOutputStream();
//发送HTTP响应结果
socketOut.write(responseFirstLine.getBytes());
//发送HTTP响应的第一行
socketOut.write(responseHeader.getBytes());
//发送HTTP响应正文
int len = 0;
buffer = new byte[128];
while(-1 != (len = in.read(buffer)))
socketOut.write(buffer, 0, len);
//睡眠1秒
Thread.sleep(1000);
//关闭TCP连接
socket.close();
}
}
示例代码使用注意
客户端请求的文件为hello1.htm,你需要在服务器端代码的class文件所在目录新建个root目录,再将文件放入该目录下,这样请求才能成功!!