HTTP协议,超文本传输协议
package com.tedu.webserver.http; import java.io.InputStream; import java.net.Socket; /** * Http请求 * 该类的每一个实例用于表示客户端发过来的一个请求内容 * @author Abner * */ public class HttpRequest { private Socket socket; private InputStream in; /* * 请求行相关信息定义 */ //请求方式 private String method; //请求的资源路径 private String url; //请求所使用的协议版本 private String protocol; /** * 初始化HttpResquest */ public HttpRequest( Socket socket){ try { this.socket=socket; //通过socket获取输出流读取客户端发送的 //请求内容 this.in=socket.getInputStream(); /* * 开始解析请求内容 * 1:解析请求行 * 2:解析消息头 * 3:解析消息正文 */ //1.解析请求行 parseRequestLine(); //2.解析消息头 parseRequestLinel(); } catch (Exception e) { } } /** * 解析请求行 */ public void parseRequestLine(){ /* * 1.通过输入流读取一行字符串,相当于 * 读取了请求行内容。 * 2.按照空格拆分请求行,可以得到相应的三部分内容 * 3.分别将methid,url,protocol设置到 * 对应的属性上完成请求行的解析工作 */ String line=readLine(); System.out.println("请求行:"+line); String[]date=line.split("\\s"); /* * 这里可能出现下标越界错误,后期优化 */ this.method=date[0]; this.url=date[1]; this.protocol=date[2]; System.out.println("method:"+method); System.out.println("url:"+url); System.out.println("protocol:"+protocol); } /** *读取一行字符串,以CRLF结尾为一行 * @param in * @return */ private String readLine(){ try { /* * 顺序从in中读取每个字符,当连续读取了cr,lf时停止。 * 并将之前读取的字符以一个字符串形式返回即可。 */ StringBuilder builder =new StringBuilder(); int d=-1; char c1='a',c2='a'; while( (d=in.read())!=-1 ){ c2=(char)d; if(c1==13&&c2==10){ break; } builder.append(c2); c1=c2; } //trim的目的是去除最后的CR符号 return builder.toString().trim(); } catch (Exception e) { } return ""; /** * 获取请求的方式 */ } public String getMethod() { return method; } /** * 获取请求的资源路径 * @return */ public String getUrl() { return url; } /** * 获取请求使用的协议版本 * @return */ public String getProtocol() { return protocol; } /** * 解析请求行 */ private void parseRequestLinel(){ /* * 1.通过输入流读取一行字符串,相当于 * 读取除了请求行 * 2.按照空格拆分请求行,可以等到对应额三部分内容 * 3.分别将methid,url,protocol设置到对应的 * 属性上完成请求行的解析工作 * */ } }
HTTP协议,超文本传输协议
a.Http协议现在使用的是1.1的版本
b.Http协议是应用层协议,底层要求使用可靠传输协议传输数据。通常传输层协议使用Tcp协议
c.Tcp协议规定两台计算机之间如何传输数据。
d.HTTP协议规定传输数据的格式,以便两台计算机之间理解对方发送的内容
e.HTTP协议规定了客户端与服务端之间的通讯模式必须遵循1次请求一次响应的通讯过程
f.首先由客户端(谁发送请求谁就是客户端)发起请求(Request),客户端通常是浏览器。然后服务端接收并输出该请求,然后给予响应(Response)
g.HTTP1.1版本相较于1.0版本的一个通讯方式的改进在于:一次Tcp连接可以进行多次请求
与响应。而1.0版本时一次Tcp连接只能进行一次请求一次响应(多次请求响应才能
完成工作是开销大,因为现在网页内容有文字内容图片内容等如果一次请求一次响应太慢,无法满足需求)。h.HTTP请求(Request)
请求有客户端发送至服务端具体格式在Http协议中有规定。
一个请求包含三部分内容:
1.请求行:请求行是一行字符串,格式为:method url protocol(CRLF)
method:请求的方式,常见的GET,POST
url:客户端希望请求的资源路径
protocol:客户端发起的请求使用的HTTP协议版本
例如:GET /index.html HTTP/1.1(CRLF)
CR:回车符,对应编码:13
LF:换行符,对应编码:10
注:回车是指回到最开始,换行是到下一行。现在的回车实际是回车加换行(Enter)
2.消息头:消息头由若干行组成,每行都是以CRLF结尾。每一行为一个具体的消息头内容。
注:实际就是客户端访问服务端时客户端将自己所用的浏览器类型等其他信息告诉服务器
格式为:
name:value(CRLF)
例如:
GET /index.html HTTP/1.1
Host: localhost:9797
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
最后一个消息头后面会跟着一个单独的CRLF(l两个crlf),表示消息头部分完毕
3.消息正文:消息正文是2进制数据,是请求中附带的用户提交的数据。
他可能是用户上传的附件,注册信息等。
当一个请求包含消息正文部分时,通常消息头中包含两个用于说明消息正文的头信息:
Content-Type :用于说明消息正文的数据是什么
Content-Length:用于说明消息正文共多少字节
HTTP协议中的请求行和消息头是文本数据,但是字符集只能是IOS8859-1规定字符(没有对中文进行编码),
所以是態附带汉子信息的。
创建WebServer服务端基本结构
创建com.tedu.webserver.core包并在包中添加主要类:WebServer
该类负责循环接收客户端的连接,并启动线程处理某个客户端的交互操作
该结构与之前聊天室服务端结构一致
线程任务有ClientHandler类完成,并定义在core包中
在ClientHandler的run方法中读取客户端发过来的内容并查看
在浏览器中输入 hppt://localhost:9797
注:9797是ServerSocket端口号
ClientHandler代码:
package com.tedu.webserver.core; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.net.Socket; import com.tedu.webserver.http.HttpRequest; /** * 线程任务类,用于处理某个客户端的请求并予以响应 * @author TEDU * */ public class ClientHandler implements Runnable { private Socket socket; public ClientHandler(Socket socket) { this.socket=socket; } @Override public void run() { System.out.println("一个客户端连接了"); try { //因为用户提交请求时消息头(提供客户端基本信息)为字节信息, //不全是2进制数据,故不能定义成字符流 //1 HttpRequest request=new HttpRequest(socket); //2 //获取请求的路径 String url=request.getUrl(); System.out.println("url:"+url); File file=new File("webapps"+url); System.out.println(file.exists()); if(file.exists()){ System.out.println("找到该文件"); /* * 将该文件内容回复给客户端 * 通过socket获取输出流,给客户端发送一个标准的HTTP响应 */ OutputStream out=socket.getOutputStream(); //发送状态行内容 String line="HTTP/1.1 200 OK"; out.write(line.getBytes("ISO8859-1")); out.write(13);// written CR out.write(10);//written LF //发送响应头 line="Content-Type: text/html"; out.write(line.getBytes("ISO8859-1")); out.write(13);// written CR out.write(10); line="Content-Length:"+file.length(); out.write(line.getBytes("ISO8859-1")); out.write(13);// written CR out.write(10); out.write(13);// written CR out.write(10); //发送响应正文 FileInputStream fis=new FileInputStream(file); byte[]data=new byte[1024*10]; int len=-1; while((len=fis.read(data))!=-1){ out.write(data, 0, len); } fis.close(); }else{ System.out.println("404"); } } catch (Exception e) { System.out.println("错误"); }finally{ //与客户端断开连接 try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } } }
WebServer代码:
package com.tedu.webserver.core; import java.net.ServerSocket; import java.net.Socket; public class WebServer { private ServerSocket server; /* * 初始化服务端 */ public WebServer(){ try { /* * Tomcat的默认服务器端口就是8080 */ server=new ServerSocket(9797); //8080被占用 } catch (Exception e) { e.printStackTrace(); } } //localhost:9797/myweb/index.html /* * 服务端启动操作 */ public void start(){ try { Socket socket=server.accept(); //启动线程处理该客户操作 ClientHandler handler=new ClientHandler(socket); Thread t=new Thread(handler); t.start(); } catch (Exception e) { // TODO: handle exception } } /* * 主方法 */ public static void main(String[] args) { WebServer server=new WebServer(); server.start(); } }
HttpRequest代码:
package com.tedu.webserver.http; import java.io.InputStream; import java.net.Socket; /** * Http请求 * 该类的每一个实例用于表示客户端发过来的一个请求内容 * @author Abner * */ public class HttpRequest { private Socket socket; private InputStream in; /* * 请求行相关信息定义 */ //请求方式 private String method; //请求的资源路径 private String url; //请求所使用的协议版本 private String protocol; /** * 初始化HttpResquest */ public HttpRequest( Socket socket){ try { this.socket=socket; //通过socket获取输出流读取客户端发送的 //请求内容 this.in=socket.getInputStream(); /* * 开始解析请求内容 * 1:解析请求行 * 2:解析消息头 * 3:解析消息正文 */ //1.解析请求行 parseRequestLine(); //2.解析消息头 parseRequestLinel(); } catch (Exception e) { } } /** * 解析请求行 */ public void parseRequestLine(){ /* * 1.通过输入流读取一行字符串,相当于 * 读取了请求行内容。 * 2.按照空格拆分请求行,可以得到相应的三部分内容 * 3.分别将methid,url,protocol设置到 * 对应的属性上完成请求行的解析工作 */ String line=readLine(); System.out.println("请求行:"+line); String[]date=line.split("\\s"); /* * 这里可能出现下标越界错误,后期优化 */ this.method=date[0]; this.url=date[1]; this.protocol=date[2]; System.out.println("method:"+method); System.out.println("url:"+url); System.out.println("protocol:"+protocol); } /** *读取一行字符串,以CRLF结尾为一行 * @param in * @return */ private String readLine(){ try { /* * 顺序从in中读取每个字符,当连续读取了cr,lf时停止。 * 并将之前读取的字符以一个字符串形式返回即可。 */ StringBuilder builder =new StringBuilder(); int d=-1; char c1='a',c2='a'; while( (d=in.read())!=-1 ){ c2=(char)d; if(c1==13&&c2==10){ break; } builder.append(c2); c1=c2; } //trim的目的是去除最后的CR符号 return builder.toString().trim(); } catch (Exception e) { } return ""; /** * 获取请求的方式 */ } public String getMethod() { return method; } /** * 获取请求的资源路径 * @return */ public String getUrl() { return url; } /** * 获取请求使用的协议版本 * @return */ public String getProtocol() { return protocol; } /** * 解析请求行 */ private void parseRequestLinel(){ /* * 1.通过输入流读取一行字符串,相当于 * 读取除了请求行 * 2.按照空格拆分请求行,可以等到对应额三部分内容 * 3.分别将methid,url,protocol设置到对应的 * 属性上完成请求行的解析工作 * */ } }
请求的页面效果图:
控制台效果图: