Web运作原理探析
-
浏览器向Web服务器请求访问一个HTML文档的过程
首先url输入,然后通过域名找到IP,先从本地hosts文件中查找,如果本地和指定DNS服务器中的缓存都没有的话, 就要请求DNS服务器从根域开始查询;得到 IP 后开始与目的主机进行三次握手来建立TCP连接;连接建立后发送HTTP/HTTPS请求,请求目标通常是一个HTML文件,服务器收到请求后,将发回一个 HTTP 响应报文,内容包括相关响应头和 HTML 正文。当浏览器收到服务器响应后,开始解析并渲染页面;传输完后与目的主机四次挥手来断开TCP连接。
-
浏览器和Web服务器各自具有的功能
浏览器:发送HTTP请求,并接收HTTP响应;解析并展示HTNL文档;解析并运行JavaScript代码
Web服务器:接收HTTP请求,并发送Http响应;动态加载并执行程序代码;
-
HTTP请求数据的基本格式
HTTP规定,HTTP请求由如下3部分组成:
-
请求方法、URI和HTTP版本。
-
请求头(Request Header)。
-
请求正文(Request Content)。
-
-
Java套接字创建HTTP客户与服务器程序
- 实现一个简单的HTTP服务器,接收客户程序发出的HTTP请求,把它打印到控制台,然后解析HTTP请求,并向客户端发回相应的HTTP响应
package server; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class HTTPServer { public static void main(String[] args) { int port; 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()); while (true){//服务器再一个无限循环中不断接收来做客户端的TCP连接请求 try { //accpet()接收一个套接字中已建立的连接 final Socket socket = serverSocket.accept(); System.out.println("建立了与客户的一个新的连接,该客户的地址为:" + socket.getInetAddress() + ":" + socket.getPort()); service(socket); } catch (Exception e){ e.printStackTrace(); System.out.println("客户端请求的资源不存在!!!"); } }//#while } catch (Exception e){ e.printStackTrace(); } } /**响应客户的HTTP请求*/ public static void service(Socket socket) throws Exception{ /**读取HTTP请求信息*/ InputStream socketIn = socket.getInputStream();//获取输入流 Thread.sleep(500);//睡眠500毫秒,等待HTTP请求 //available()这个方法可以在读写操作前先得知数据流里有多少个字节可以读取 int size = socketIn.available(); //缓存字节的数组 byte[] buffer = new byte[size]; socketIn.read(buffer); String request = new String(buffer); System.out.println("==== 请求数据 ===="); System.out.println(request);//打印请求数据 /**解析HTTP请求*/ //获得HTTP请求的第一行 int endIndex = request.indexOf("\r\n"); if (endIndex == -1){ endIndex = request.length(); } String firstLineOfRequest = request.substring(0,endIndex); String[] parts = firstLineOfRequest.split(" ");//第一行是以空格隔开的,所以以空格拆分 String requestMethod = ""; String uri = ""; String protocol = ""; if (parts.length >= 2){ requestMethod = parts[0]; uri = parts[1]; //获取请求中的uri protocol = parts[2]; } /** 确定响应正文类型 */ String contentType; if (uri.indexOf("html") != -1 || uri.indexOf("htm") != -1){ contentType = "text/html"; } else if (uri.indexOf("jpg") != -1 || uri.indexOf("jpeg") != -1){ contentType = "image/jpeg"; } else if (uri.indexOf("gif") != -1){ contentType = "image/gif"; } else { contentType = "application/octet-stream";//字节流类型 } /**创建HTTP响应结果*/ //HTTP响应的第一行 String responseFirstLine = "HTTP/1.1 200 OK\r\n"; //HTTP响应头 String responseHeader = "ContentType:" + contentType+"\r\n\r\n"; //获得读取响应正文数据的输入流 InputStream in = HTTPServer.class.getResourceAsStream("root/" + uri); /**发送响应结果*/ OutputStream socketOut = socket.getOutputStream();//获取输出流 //发送HTTP响应的第一行 socketOut.write(responseFirstLine.getBytes()); //发送响应头 socketOut.write(responseHeader.getBytes()); //发送HTTP响应正文 int len = 0; //读取的字节长度,为-1时表示没有 buffer = new byte[128]; while ((len = in.read(buffer)) != -1){ socketOut.write(buffer,0,len); } Thread.sleep(1000);//睡眠1秒,等待客户端接收HTTP响应结果 socket.close();//关闭TCP连接 } }
-
一个简单的HTTP客户程序,它以GET方式向HTTP服务器发送HTTP请求,然后把接收的HTTP响应结果打印到控制台
package client; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class HTTPClient { public static void main(String args[]){ //确定HTTP请求的uri String uri="index.htm"; if(args.length !=0)uri=args[0]; doGet("localhost",8080,uri); //按照GET请求方式访问HTTPServer } /** 按照GET请求方式访问HTTPServer */ public static void doGet(String host,int port,String uri){ Socket socket=null; try{ socket=new Socket(host,port); //与HTTPServer建立FTP连接 }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()); Thread.sleep(2000); //睡眠2秒,等待响应结果 /*接收响应结果*/ 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();} } } //#doGet() }
-
结果如图
-
HTTP响应结果的基本格式
HTTP响应也由3部分组成:
-
HTTP版本、状态代码和描述
-
响应头(Response Header)
-
响应正文(Response Content)
-
-
浏览器以及Web服务器端对HTTP请求参数的处理过程
-
对于GET请求方式,请求参数跟在URI的后面
GET /servlet/HelloServlet?username=zs&password=1234 HTTP/1.1
-
对于POST请求方式,请求参数将作为HTTP请求的正文部分
POST /servlet/HelloServlet HTTP/1.1 Accpet:image/gif,image/jpeg,*/* ... Cookie:style=default username=Tom&password=123456
-
-
浏览器向Web服务器端上传文件的过程
HTTP响应正文部分最常见的是HTML文档,此外还可以是其他任意格式的数据,如图片和声音文件中的数据。同样HTTP请求正文部分也不仅仅是字符串格式的参数,也可以是其他任意格式的数据。Web服务器将特定的文件数据放在响应正文中就能向浏览器发送,浏览器也可以将任意格式的数据放入请求正文中发送给Web服务器。
<from></from>的重要属性:
method属性:指定请求方式为“POST”,那么表单的数据就会放到HTTP的请求正文部分。
enctype:用于指定表单数据的MIME类型,取为 “MULITPART/FORM-DATA”表示表单数据为复合类型数据,包含多个子部分。
action:指定用户提交数据是的URI。<html> <head> <title>HelloWorld</title> </head> <body > <form name="uploadForm" method="POST" enctype="MULTIPART/FORM-DATA" action="servlet/UploadServlet"> <table> <tr> <td><div align="right">File Path:</div></td> <td><input type="file" name="filedata" /> </td> </tr> <tr> <td><input type="submit" name="submit" value="upload"></td> <td><input type="reset" name="reset" value="reset"></td> </tr> </table> </form> </body> </html>
通过上述表单上传文件FromClient.txt。
文件中的内容:Data1 in FromClient.txt Data2 in FromClient.txt Data3 in FromClient.txt Data4 in FromClient.txt
以下是上传上述文件后获取到的HTTP请求内容:
POST /servlet/UploadServlet HTTP/1.1 //HTTP请求的第一行 Host: localhost:8080 //HTTP请求头开始 Connection: keep-alive Origin: http://localhost:8080 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqDu5O5PyBZGVIqQF //设定边界取值 ..... Cookie: yiShengHuShiZhan_tm= //HTTP请求头最后一行 空行,以下部分是HTTP的请求正文 ------WebKitFormBoundaryqDu5O5PyBZGVIqQF //边界 Content-Disposition: form-data; name="filedata"; filename="FromClient.txt" //文件头部分开始 Content-Type: text/plain 空行 以下是正文部分 Data1 in FromClient.txt Data2 in FromClient.txt Data3 in FromClient.txt Data4 in FromClient.txt 空行 ------WebKitFormBoundaryqDu5O5PyBZGVIqQF //边界 Content-Disposition: form-data; name="submit" //提交按钮的头 空行 upload //提交按钮的正文部分即提交按钮的参数值 ------WebKitFormBoundaryqDu5O5PyBZGVIqQF-- //边界。
以上HTTP请求的正文部分为复合类型,它包含练个子部分:文件部分和提交按钮部分。浏览器会随机产生一个字符串形式的边界(boundary),在HTTP请求头中,一下代码设定了边界的取值:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqDu5O5PyBZGVIqQF
HTTP请求正文部分的各个子部分用边界来进行分割。每个子部分由头和正文部分组成。包含符合类型的HTML表单数据的HTTP请求组成结构解析这类型的HTTP请求时,先确定边界值根据请求头中 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqDu5O5PyBZGVIqQF。然后根据边界值来定位文件部分,然后再定位到文件的正文部分。