java实现web服务器.docx
《java实现web服务器.docx》由会员分享,可在线阅读,更多相关《java实现web服务器.docx(19页珍藏版)》请在冰豆网上搜索。
java实现web服务器
TheHypertextTransferProtocol(HTTP)
它是一个请求、响应协议--客户端发出一个请求,服务器响应这个请求。
HTTP运用可靠的TCP连接,通常用的TCP80端口。
它的第一个版本是HTTP/
0."9,然后被HTTP/
1."0取代。
当前的版本是HTTP/
1."1,由RFC2616(.pdf)定义。
本节主要对应HTTP
1."1,足够使你充分理解由Web服务器程序发出的消息。
如果你对更加详细的知识有兴趣,可以参考RFC
2616。
"
在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。
服务器不能主动去与客户端联系,也不能给客户端发出一个回叫连接。
客户端与服务器端都可以提前中断一个连接。
例如,当用一个浏览器下载一个文件时,你可以通过点击“停止”键来中断文件的下载,关闭与服务器的HTTP连接。
HTTP请求
一个HTTP请求包含三个部分:
Method-URI-Protocol/Version方法-地址-版本
Requestheader请求头
Entitybody请求实体
下面是一个HTTP请求实例:
POST/servlet/default.jspHTTP/
1."1
Accept:
text/plain;text/html
Accept-Language:
en-gb
Connection:
Keep-Alive
Host:
localhost
Refer:
http:
//localhost/ch8/SendDetails.htm
User-Agent:
Mozilla/
4."01;Windows98)
Content-Length:
33
Content-Type:
Accept-Encoding:
gzip,deflate
LastName=Franks&FirstName=Michael
TheMethod-URI-Protocol/Version在这个请求的第一行:
POST/servlet/default.jspHTTP/
1."1
其中POST是请求的类型。
每个客户端HTTP请求可以是HTTP规范中指定的许多请求类型中的一种。
HTTP
Version指的是该HTTP请求所用到的HTTP协议版本。
在上面的HTTP请求中,实体只是简单以下的一行:
LastName=Franks&FirstName=Michael
在一个典型的HTTP请求中,请求实体内容会长得多。
HTTP响应
与请求相似,HTTP响应也由三部分组成:
Protocol-Statuscode-Description协议状态描述代码
Responseheaders响应头
Entitybody响应实体
以下是一个HTTP响应的实例:
HTTP/
1."1200OK
Server:
Microsoft-IIS/
4."0
Date:
Mon,3Jan199813:
13:
33GMT
Content-Type:
text/html
Last-Modified:
Mon,11Jan199813:
23:
42GMT
Content-Length:
112
响应头的第一行类似请求头的第一行,告诉你所用的协议是HTTP
1."1,请求成功(200=success),以及没有任何问题。
响应头类似请求头也包含了一些有用的信息。
响应的实体响应本身的HTML内容。
头与实体之间由回车换行的空行(CRLF)分开。
Socket类
要创建一个socket,你可以用Socket类中几个构建方法中的一个。
其中一个接受主机名与端口号作为参数:
一旦你成功地创建了一个Socket类的实例,你就可以用它去发送与接收字节流了。
要发送字节流,你需要呼叫Socket类的getOutputStream方法来得到一个java.io.OutputSteam对象。
要发送文本到远程的程序,你通常需要从返回的OutputStream创建一个java.io.PrintWriter对象。
要从连接的另一端接收字节流,你需要呼叫Socket类的getInputStream方法,它返回一个java.io.InputStream对象。
以下代码创建一个可以与本地HTTP服务器通信的socket(
127."
0.
0."1表示一个本地的主机),发送一个HTTP请求,并接收从服务器的响应。
它还创建一个StringBuffer对象来接受响应,并打印到控制台。
Socketsocket=newSocket("
127."
0.
0."1","80");
OutputStreamos=socket.getOutputStream();
booleanautoflush=true;
PrintWriterout=newPrintWriter(socket.getOutputStream(),
autoflush);
BufferedReaderin=newBufferedReader(
newInputStreamReader(socket.getInputStream()));
//sendanHTTPrequesttothewebserver
out.println("GET/index.jspHTTP/
1."1");
out.println("Host:
localhost:
80");
out.println("Connection:
Close");
out.println();
//readtheresponse
booleanloop=true;
StringBuffersb=newStringBuffer
(8096);
while(loop){
if(in.ready()){
inti=0;
while(i!
=-1){
i=in.read();
sb.append((char)i);}loop=false;}Thread.currentThread().sleep
(50);}//displaytheresponsetotheoutconsole
System.out.println(sb.toString());
socket.close();
注意要从web服务器得到正确的响应,你必须要发送用HTTP协议编译了的HTTP请求。
如果你看了上面的HTTP部分,你应该能够理解上面代码中的HTTP请求。
编者注:
基于Java的Web服务器工作原理2
fajaven译发文时间:
2003."0
9."1217:
00:
38
ServerSocket类
Socket类描述的是“客户端”socket,当你需要创建与远程服务程序连接时需要用到它。
如果你想实现一个服务程序,如HTTP服务器或者FTP服务器,则需要另外不同的方法。
要创建服务器端socket,需要用到ServerSocket类提供的四个构建方法中的一个。
你需要指定服务器端socket侦听的IP地址与端口号。
比较典型地,这个IP地址可以是12
7."
0.
0."1,意思是该服务器端socket侦听的是本地机器。
服务器端socket侦听的IP地址指的是绑定地址。
服务器端socket另一个重要的属性是队列长度,即它拒绝请求前所接受的最大请求排队长度。
ServerSocket类的构建方法之一如下:
127."
0.
0."1");
以下行的代码创建了一个服务器端socket,它侦听本地机器的80端口,限制队列长度为
1。
"
127."
0.
0."1"));
一旦有了一个ServerSocket实例,就可以通过呼叫其accept方法来让它等待进来的链接请求。
这个方法只有当接收到请求时才返回,它返回的是Socket类的实例。
这个Socket对象就可以用来从客户端应用程序发送与接收字节流,正如上节据说的那样。
实际上,accept方法是本文例子中用到的唯一方法。
应用实例
我们的web服务器程序是ex
01."pyrmont包的一部分,它包含三个类:
HttpServer;Request;Response。
整个程序的入口(静态main方法)是HttpServer类。
它创建一个HttpServer的实例,并呼叫其await方法。
正如名字表达的,await在一个特定的端口等待HTTP请求,处理它们,并返回响应给客户端。
它保持等待状态,直到收到停止命令。
(用方法名await代替wait,是因为System中有一个重要的与线程相关的方法)
这个程序只从一个特定的目录发送静态资源,如HTML与图像文件。
它只支持没有文件头(如日期与cookie)的情况。
现在我们将在如下的几节中看一下这三个类。
HttpServer类
HttpServer实现了一个web服务器,它可以提供(serve)特定目录及其子目录下的静态资源。
这个特定的目录由publicstaticfinalWEB_ROOT指定。
WEB_ROOT初始化如下:
publicstaticfinalStringWEB_ROOT=
System.getProperty("user.dir")+File.separator+"webroot";
代码列表中包含了一具叫做webroot的目录,里面有一些静态的资源,你可以用来测试本应用。
你也可以看到一个servlet,在我的下一篇文章将会被用到:
“Servlets容器是怎样工作的”。
为了请求一个静态的资源,在浏览器的地址栏输入如是地址:
http:
//machinename:
port/staticResources
如果你从不同的机器上发送请求到运行本应用的机器,则machinename是运行应用机器的机器名或IP地址,port是80,staticResources是被请求的文件名称,它必须包含在WEB_ROOT目录内。
例如,如果你用同一台电脑来测试这个应用,你想要HttpServer发送index.html这个文件,用以下的地址:
http:
//localhost:
80/index.html
要停止服务,只需要从浏览器发送一个停止(shutdown)命令,即在浏览器的地址栏输入host:
port字段后,加上预先定义好的字符串。
在我们的HttpServer类中,停止命令被定义为SHUTDOWN,一个staticfinal变量。
privatestaticfinalStringSHUTDOWN_COMMAND="/SHUTDOWN";
因此,要停止服务,你可以这样:
http:
//localhost:
80/SHUTDOWN
现在,让我们看一下列表
1."1中给出的await方法。
代码列表后面将对这段代码做一些解释。
Listing
1."
1.TheHttpServerclass'awaitmethod
publicvoidawait(){
ServerSocketserverSocket=null;
intport=80;
try{
serverSocket=newServerSocket(port,1,
127."
0.
0."1"));}catch(IOExceptione){
e.printStackTrace();
System.exit
(1);}//Loopwaitingforarequest
while(!
shutdown){
Socketsocket=null;
InputStreaminput=null;
OutputStreamoutput=null;
try{
socket=serverSocket.accept();
input=socket.getInputStream();
output=socket.getOutputStream();
//createRequestobjectandparse
Requestrequest=newRequest(input);
request.parse();
//createResponseobject
Responseresponse=newResponse(output);
response.setRequest(request);
response.sendStaticResource();
//Closethesocket
socket.close();
shutdown=request.getUri().equals(SHUTDOWN_COMMAND);}catch(Exceptione){
e.printStackTrace();
continue;}}}await方法以创建一个ServerSocket实例开始,然后进入一个while的循环。
serverSocket=newServerSocket(
127."
0.
0."1"));
...
//Loopwaitingforarequest
while(!
shutdown){
...}在while循环中的代码,运行到ServerSocket的accept方法即停止。
这个方法只有在80端口接收到HTTP请求才返回:
socket=serverSocket.accept();
收到请求后,await方法从accept方法返回的Socket实例中等到java.io.InputStream与java.io.OutputStream:
input=socket.getInputStream();
output=socket.getOutputStream();
然后await方法创建一个Request对象,呼叫它的parse方法来解析这个原始的HTTP请求:
//createRequestobjectandparse
Requestrequest=newRequest(input);
request.parse();
下一步,await方法创建一个Response对象并把Request对象设置给它,呼叫它的sendStaticResource方法:
//createResponseobject
Responseresponse=newResponse(output);
response.setRequest(request);
response.sendStaticResource();
最后,await方法关闭Socket,呼叫Request的getUri方法来检查HTTP请求的地址是否是一个停止命令。
如果是,则shutdown变量被设置为true,程序退出while循环:
//Closethesocket
socket.close();
shutdown=request.getUri().equals(SHUTDOWN_COMMAND);
基于Java的Web服务器工作原理3
fajaven发文时间:
2003."0
9."1217:
11:
54
Request类
Request类对应HTTP请求。
创建这个类的实例,并传给它从Socket获得的InputStream对象,从而捕获与客户端的通信。
呼叫InputStream对象的read方法中的一个就可以得到HTTP请求的原始数据。
Request类有二个public方法parse与getUri。
parse方法解析HTTP请求的原始数据。
它做的事情不多--唯一它使之有效的信息是HTTP请求的URI,这个通过呼叫私有方法parseUri来获得。
parseUri方法把URI作为一个变量。
调用getUri方法可以得到HTTP请求的URI。
要明白parse与parseUri的工作原理,你需要知道HTTP请求的结构,由RFC2616定义。
一个HTTP请求包括三个部分:
Requestline;Headers;Messagebody。
现在,我们只需要关注HTTP请求的第一部分--请求行。
请求行以方法记号开始,接着是请求的URI与协议版本,以回车换行符结束。
请求行的元素之间以空格分开。
例如,一个用GET方法的index.html文件的请求行如下:
GET/index.htmlHTTP/
1."1
parse方法从socket的InputStream传递给Request对象中读取字节流,把这个字节数组存在缓冲里。
然后,它把buffer字节数组里的字节放入叫做request的StringBuffer对象中,再把StringBuffer替换成String传递给parseUri方法。
parse方法的代码如列表
1."2
Listing
1."
2.TheRequestclass'parsemethod
publicvoidparse(){
//Readasetofcharactersfromthesocket
StringBufferrequest=newStringBuffer
(2048);
inti;
byte[]buffer=newbyte[2048];
try{
i=input.read(buffer);}catch(IOExceptione){
e.printStackTrace();
i=-1;}for(intj=0;jrequest.append((char)buffer[j]);}System.out.print(request.toString());
uri=parseUri(request.toString());}parseUri方法查找请求行的第一个与第二个空格,从而从请求行获得了URI。
列表
1."3展示了parseUri方法的代码。
Listing
1."
3.TheRequestclass'parseUrimethod
privateStringparseUri(StringrequestString){
intindex1,index2;
index1=requestString.indexOf('');
if(index1!
=-1){
index2=requestString.indexOf('',index1+1);
if(index2>index1)
returnrequestString.substring(index1+1,index2);}returnnull;}Response类
Response类描述HTTP响应。
它的构建方法接受OutputStream对象,如下:
publicResponse(OutputStreamoutput){
this.output=output;}Response对象通过传递从socket获得的OutputStream对象到HttpServer类的await方法而创建。
Response类有二个公共方法setRequest与setStaticResource。
setRequest用来传递Request对象到Response对象。
它比较简单,代码如列表
1."4所示:
Listing
1."
4.TheResponseclass'setRequestmethod
publicvoidsetRequest(Requestrequest){
this.request=request;}sendStaticResource方法用来发送静态的资源,例如HTML文件。
它的实现如列表
1."5所示:
Listing
1."
5.TheResponseclass'sendStaticResourcemethod
publicvoidsendStaticResource()throwsIOException{
byte[]bytes=newbyte[BUFFER_SIZE];
FileInputStreamfis=null;
try{
Filefile=newFile(HttpServer.WEB_ROOT,request.getUri());
if(file.exists()){
fis=newFileInputStream(file);
intch=fis.read(bytes,0,BUFFER_SIZE);
while(ch!
=-1){
output.write(bytes,0,ch);
ch=fis.read(bytes,0,BUFFER_SIZE);}}
else{
//filenotfound
StringerrorMessage="HTTP/
1."1404FileNotFound\r\n"+
"Content-Type:
text/html\r\n"+
"Content-Length:
23\r\n"+
"\r\n"+"FileNotFound
";
output.write(errorMessage.getBytes());}}
catch(Exceptione){
//thrownifcannotinstantiateaFileobject
System.out.println(e.toString());}finally{
if(fis!
=null)
fis.close();}}
SendStaticResource方法非常简单。
它首先通过传递父与子目录到File类的构建方法从而实例化java.io.File类。
FilefilenewFile(HttpServer.WEB_ROOT,request.getUri());
然后检查这个文件是否存在。
如果存在,则sendStaticResource方法传递File对象创建java.io.FileInputStream对象。
然后调用FileInputStream的read方法,并把字节数组写到OutputStream对象output。
就这样,静态资源的内容作为原始数据被发送到浏览器。
if(file.exists()){
fis=newFileInputStream(file);
intch=fis.read(bytes,0,BUFFER_SIZE);
while(ch!
=-1){
output.write(bytes,0,ch);
ch=fis.read(bytes,0,BUFFER_SIZE);}}
如果文件不存在,sendStaticResource发送一个错误信息到浏览器。
StringerrorMessage="HTTP/
1."1404FileNotFound\r\n"+
"Content-Type:
t