HTTP学习与Web服务器编程
来源:互联网 发布:中世纪2王国战役优化9 编辑:程序博客网 时间:2024/06/05 11:57
这次的主题是查找HTTP协议的相关资料,基于此编写一个简单的Web服务器。
需要完成的几大主要的要求有:
1)编写一个简单的Web服务器;
2)实现的服务器应能与标准的浏览器进行简单的交互;
3)记录浏览器与服务的交互过程;
4)利用HTML语言编写网页浏览器可通过编写的Web服务器正常访问该网页;
5)支持多用户并发访问;
6)扩展编写的简单Web服务器,使浏览器能够浏览Web上存储的图像
一.了解http协议(参考百度百科)
HTTP是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。(我们称这个客户端)叫用户代理,应答的服务器上存储着(一些)资源,比如HTML文件和图像;(我们称)这个应答服务器为源服务器。“客户”与“服务器”是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。基于HTTP协议的客户/服务器模式的信息交换过程,它分四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。
HTTP协议是基于请求/响应范式的。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为,统一资源标识符、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应信息,其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
其实简单说就是任何服务器除了包括HTML文件以外,还有一个HTTP驻留程序,用于响应用户请求。你的浏览器是HTTP客户,向服务器发送请求,当浏览器中输入了一个开始文件或点击了一个超级链接时,浏览器就向服务器发送了HTTP请求,此请求被送往由IP地址指定的URL。驻留程序接收到请求,在进行必要的操作后回送所要求的文件。在这一过程中,在网络上发送和接收的数据已经被分成一个或多个数据包,每个数据包包括:要传送的数据;控制信息,即告诉网络怎样处理数据包。TCP/IP决定了每个数据包的格式。如果事先不告诉你,你可能不会知道信息被分成用于传输和再重新组合起来的许多小块。
HTTP报文由从客户机到服务器的请求和从服务器到客户机的响应构成。
请求报文格式为:请求行 - 通用信息头 - 请求头 - 实体头 - 报文主体。请求行以方法字段开始,后面分别是 URL 字段和 HTTP 协议版本字段,并以 CRLF 结尾。SP 是分隔符。除了在最后的 CRLF 序列中 CF 和 LF 是必需的之外,其他都可以不要。有关通用信息头,请求头和实体头方面的具体内容可以参照相关文件。
应答报文格式为:状态行 - 通用信息头 - 响应头 - 实体头 - 报文主体。状态码元由3位数字组成,表示请求是否被理解或被满足。原因分析是对原文的状态码作简短的描述,状态码用来支持自动操作,而原因分析用来供用户使用。客户机无需用来检查或显示语法。有关通用信息头,响应头和实体头方面的具体内容可以参照相关文件。
简而言之,使用http就像我们打电话订货一样,我们可以打电话给商家,告诉他我们需要什么规格的商品,然后商家再告诉我们什么商品有货,什么商品缺货。这些,我们是通过电话线用电话联系(HTTP是通过TCP/IP),当然我们也可以通过传真,只要商家那边也有传真。
二.建立简单的web服务器
我们将使用java语言,基于java.net.Socket和java.net.ServerSocket实现一个简单的web服务器。
首先我们来看一下整个程序的大致结构(使用的IDE为eclipse):
有三个Java类,分别是服务器类MyWebServer.java和需要在服务器类里调用的Request类和Responses类,分别用来处理接收到的http协议报文的解析工作和响应工作。在本工程目录下建一个文件夹resource用来储存所有的html文件和图片。
现在主要说说整个程序的思路:
1.创建一个ServerSocket对象;
2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析
5.处理响应:根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;
6.关闭Socket对象;
7.转到步骤2,继续等待连接请求;
(1)服务器类
//MyWebServer.javapackage homework3;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.PrintStream;import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket;public class MyWebServer { public static int PORT=8888; public static final String WEB_ROOT="resource"; public static void main(String[] args) { System.out.println("开启服务器"); try { ServerSocket WebServer=null; WebServer = new ServerSocket(PORT,1,InetAddress.getByName("127.0.0.1")); while(true) //不断循环监听是否有新的请求,有的话启动一个线程响应 { Socket Client=WebServer.accept(); new HttpConnectThread(Client).start(); } } catch (IOException e) { e.printStackTrace(); } } }class HttpConnectThread extends Thread{ private Socket Client; public HttpConnectThread(Socket s) { Client=s; } public void run() { try { InputStream input=null; OutputStream output=null; input=Client.getInputStream(); output=Client.getOutputStream(); //从Socket对象中获取InputStream和OutputStream字节流, //这两个流分别对应request请求和response响应; Request request=new Request(input); if(request.parse(Client)==1){ //处理请求:读取InputStream字节流信息,转成字符串形式,并解析 Response response=new Response(output); response.setRequest(request); response.sendStaticResource(Client); } //处理响应:根据解析出来的信息,从WEB_ROOT目录中寻找请求的资源资源文件, //读取资源文件,并将其写入到OutputStream字节流中; Client.close(); } catch (IOException e) { e.printStackTrace(); } }}
简单的说一下这一块:
先是定义了服务器的端口port(8888)和基目录webroot(resource),然后利用ServerSocket建立了一个本机上8888端口的服务器。为了完成要求5)支持多用户并发访问,我们在这边做了两件事,首先用一个while循环不断地监听是否有对应本服务器的请求,第二件事对于每一个请求访问的ip地址,新建一个线程与其进行交互。所以while里边就是利用新建一个socket并且让他WebServer.accept()去监听别人的请求,没听到就阻塞在这里一直监听,听到了new一个我们写好的线程HttpConnectThread并且start它。
再来看看这个线程的run函数。首先利用socket的getInputStream()和getOutputStream()从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应。request处理请求主要工作为读取InputStream字节流信息,转成字符串形式,并解析。Response处理响应会根据解析出来的信息,从WEB_ROOT目录中寻找请求的资源文件,读取资源文件,并将其写入到OutputStream字节流中。需要说明的是这里我使用if(request.parse(Client)==1)这句话是因为这里出现的一个小bug,后边我在说明request类再详细说明。
(2)请求类
//request.javapackage homework3;import java.io.IOException;import java.io.InputStream;import java.io.PrintStream;import java.net.Socket; public class Request { private InputStream input; private String uri; public Request(InputStream input){ this.input=input; } public int parse(Socket socket){ //Read a set of characters from the socket StringBuffer request=new StringBuffer(2048); int i; byte[] buffer=new byte[2048]; try { i=input.read(buffer); } catch (Exception e) { e.printStackTrace(); i=-1; } for(int j=0;j<i;j++){ request.append((char)buffer[j]); } System.out.print(request.toString()); uri=parseUri(request.toString()); if(request.toString().split("\n")[0].contains("html")||request.toString().split("\n")[0].contains("jpg")){ return 1; } else{ // 下面是由服务器直接生成的主页内容 // 1、首先向浏览器输出响应头信息 PrintStream out; try { out = new PrintStream(socket.getOutputStream(), false, "GB2312"); out.println("HTTP/1.1 200\r"); out.println("Content_Type:text/html\r"); out.println("");//报文头和信息之间要空一行 // 2、输出主页信息 out.println("<HTML><BODY>"+ "<center>"+ "<H1>HTTP协议测试服务器"+ "</H1>"+ "<form method='get' action='http://127.0.0.1:8888/'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='GET测试'/></form><br/>"+ "<form method='post' enctype='text/plain' action='http://127.0.0.1:8888/'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='POST测试'/></form><br/>"+ "</center>您提交的数据如下:<pre>" + request.toString()+ "</pre></BODY></HTML>"); out.flush(); out.close(); System.out.println("msg.toString() "+request.toString()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return 1; } public String parseUri(String requestString){ int index1,index2; index1=requestString.indexOf(" "); if(index1!=-1){ index2=requestString.indexOf(" ",index1+1); if(index2>index1){ return requestString.substring(index1+1,index2); } } return null; } public String getUri(){ return this.uri; } }
这个程序我调了很久的bug,因为本来并没有写html文件,而是像上边程序中一样直接通过printStream输出流往socket的getOutputStream输出html语句就可以出现对应的网页,但是在显示图片的时候出现了坑爹的情况。首先使用绝对路径的时候浏览器会报错“Not allowed to load local resource”,究其原因是浏览器基于安全考虑不允许直接访问。换成相对路径吧,服务器当前工作路径下建一个resource放图片,写地址用/resource/xxx.jpg。好么,图片死活不出来,在浏览器按F12审查了半天也没审查个所以然来,感觉就是传过去的文件type是text/html类型的而不是jpg类型的(但是最后改完可以显示了我一看还是text/html类型),总之经历了很久debug的绝望以后我换成了现在这种写法,即要显示图片的话还是老老实实写一个html页面,如果是动态显示记录的浏览器与服务的交互过程(是的本程序你不知可以在console里看,在浏览器也可以直接看),没涉及图片输出,用我原来的想法。这就解释了我为什么要把require返回给服务器的return分为1和0了,解析报文如果有请求图片和html文件,那么返回1,调用response回应对应文件,否则返回0不调用response的内容,而是直接通过printStream输出html语句动态显示get和post过程中的报文协议。
(3)回应请求类
//response.Javapackage homework3;import java.io.File; import java.io.FileInputStream; import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.PrintStream;import java.net.Socket; /** * HTTP Response = Status-Line * *(( general-header | response-header | entity-header ) CRLF) * CRLF * [message-body] * Status-Line=Http-Version SP Status-Code SP Reason-Phrase CRLF * */ public class Response { private static final int BUFFER_SIZE=1024*1024; Request request; OutputStream output; public Response(OutputStream output){ this.output=output; } public void setRequest(Request request){ this.request=request; } public void sendStaticResource(Socket socket)throws IOException{ byte[] bytes=new byte[BUFFER_SIZE]; FileInputStream fis=null; try { File file=new File(MyWebServer.WEB_ROOT,request.getUri()); if(file.exists()){ fis=new FileInputStream(file); int ch=fis.read(bytes,0,BUFFER_SIZE); String header = "HTTP/1.1 200\r\n" + "Content-Type: text/html\r\n"+ "Content-Length: " + file.length() + "\r\n" + "\r\n"; output.write(header.getBytes()); while(ch!=-1){ output.write(bytes, 0, BUFFER_SIZE); ch=fis.read(bytes, 0, BUFFER_SIZE); } }else{ //file not found String errorMessage="HTTP/1.1 404 File Not Found\r\n"+ "Content-Type:text/html\r\n"+ "Content-Length:23\r\n"+ "\r\n"+ "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } } catch (Exception e) { System.out.println(e.toString()); }finally{ if(fis!=null){ output.close(); fis.close(); } } } }
(2)(3)两个类就不详细一句句说明了,就着注释应该很容易明白。
三.实现效果
点开MyWebServer文件,点击运行,console会跳出:
随便打开一个浏览器(我用的是chrome),输入127.0.0.1:8888
在get和post随便输入数据点击按钮,可以观察下边的数据变化情况
OK~如果想要访问图片和服务器里的html文件,输入127.0.0.1:8888/index.html
根据页面内容点击链接可以访问不同的文件。当然回头看一下console里面记录着所有的protocol信息。
最后附上index.html和index2.html(图片大家自己找啦)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Web服务器</title> </head> <body> <h1>This is yaozonghai's webserver</h1> <img src="pic\a3.jpg" /><br> <h5>This is a simple index.Your request will be sent to my WebServer</h5> <p>you can click <a href="index2.html"> more image</a> to scan picture on web<p> <img src="pic\a5.jpg" /><br> <p>you can click <a href="xxx"> observe the http protocol</a> to observe the http protocol<p> <h3>Thank you for using<h3></body> </html>
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>LBJ</title> </head> <body> <h1>so handsome the man is</h1> <img src="pic\a1.jpg" /> <img src="pic\a2.jpg" /> <img src="pic\a3.jpg" /> <img src="pic\a4.jpg" /> <a href="index.html"> 返回</a></body> </html>
That’s all,thank you!
- HTTP学习与Web服务器编程
- web服务器与http协议
- 图解HTTP协议 第5章 与HTTP协作的Web服务器学习笔记
- 《图解HTTP》学习笔记(五)-与HTTP协作的Web服务器
- 图解http学习笔记5.与HTTP协作的web服务器
- 嵌入式Web服务器学习之HTTP协议
- 5 与HTTP协作的web服务器
- 与Http协作的Web服务器
- HTTP服务器/Web服务器
- 图解HTTP第五章:与HTTP协作的Web服务器
- HTTP详解--与HTTP协作的Web服务器
- HTTP学习笔记:Web与网络基础
- 图解HTTP:与HTTP协作的WEB服务器与HTTP首部
- http web服务器
- http web 应用服务器
- http web服务器
- Java Web学习(1):Web应用程序与Web服务器
- HTTP学习笔记之二——Web服务器
- 彩色图像的曲线灰度变化(伽马变换)
- C语言05
- Java内存模型与垃圾回收
- 17.11.19日报
- 球员题(查询+添加+排序)
- HTTP学习与Web服务器编程
- mysql id自动增加和时间戳
- 5. 最高的分数
- SpringMVC数据绑定
- 机器学习基础—Kaggle泰坦尼克预测(完整分析)
- 自定义markdown编辑器
- DOS之找不到桌面
- 信息题(查询+批量删除+修改+添加)
- netease-cloud-music for ubuntu installation