昆明理工大学 WEB服务与分布式计算 实验二.docx
《昆明理工大学 WEB服务与分布式计算 实验二.docx》由会员分享,可在线阅读,更多相关《昆明理工大学 WEB服务与分布式计算 实验二.docx(15页珍藏版)》请在冰豆网上搜索。
昆明理工大学WEB服务与分布式计算实验二
昆明理工大学信息工程与自动化学院学生实验报告
(2014—2015学年第二学期)
课程名称:
Web服务与分布计算实验室:
信自楼2342015年4月28日
专业、年级、班
计科122班
学号
201210405204
姓名
邹华宇
成绩
实验项目名称
实验二实现一个基本的WEB服务器程序
指导教师
王红斌
教
师
评
语
该同学是否了解实验原理:
A.了解□B.基本了解□C.不了解□
该同学的实验能力:
A.强□B.中等□C.差□
该同学的实验是否达到要求:
A.达到□B.基本达到□C.未达到□
实验报告是否规范:
A.规范□B.基本规范□C.不规范□
实验过程是否详细记录:
A.详细□B.一般□C.没有□
教师签名:
年月日
一、实验目的
熟悉Socket通讯原理和理解HTTP协议,了解WEB服务器的工作原理。
二、实验要求
采用SocketAPI知识和对HTTP协议,CGI(CommonGatewayInterface,通用网关界面)的理解,实现一个基本的WEB服务器程序,要求服务器能成功响应客户程序发来的GET命令(传输文件),进一步实现响应POST和GET命令的CGI程序用请求。
三、实验原理与步骤
(1)服务器主要监听来至客户浏览器或是客户端程序的连接请求,并且接受到客户请求后对客户请求作出响应。
如果请求时静态的文本或是网页则将内容发送给客户。
如何是CGI程序则服务器调用请求的CGI程序,并发送结果给客户。
(2)HTTP协议是基于TCP/IP协议之上的协议,是WEB浏览器和WEB服务器之间的应用层协议,是通用的、无状态的、面向对象的协议。
(3)HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。
GET命令的格式为:
GET路径/文件名HTTP/1.0
文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。
(4)WEB浏览器提交请求后,通过HTTP协议传送给WEB服务器。
WEB服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。
在发送内容之前Web服务器首先传送一些HTTP头信息:
HTTP1.0200OK
WEBServer:
1.0
content_type:
类型
content_length:
长度值
(5)响应POST和GET命令的CGI程序调用请求需要服务器执行外部程序,Java执行外部可执行程序的方法是:
首先通过Runtimerun=Runtime.getRuntime()返回与当前Java应用程序相关的运行时对象;然后调用ProcessCGI=run.exec(ProgramName)另启一个进程来执行一个外部可执行程序。
四、Web服务器的实现步骤
(1)创建ServerSocket类对象,监听端口8080。
这是为了区别与HTTP的标准TCP/IP端口80而取的;
(2)等待、接受客户机连接到端口8080,得到与客户机连接的socket;
(3)创建于socket字相关联的输入流和输出流
(4)从与socket关联的输入流instream中读取一行客户机提交的请求信息,请求信息的格式为:
GET路径/文件名HTTP/1.0
(5)从请求信息中获取请求类型。
如果请求类型是GET,则从请求信息中获取所访问的文件名。
没有HTML文件名时,则以index.html作为文件名;
(6)如果请求文件是CGI程序则调用它,并把结果通过socket传回给Web浏览器,然后关闭文件。
否则发送错误信息给浏览器。
(7)关闭与相应web浏览器的socket字。
五、结果分析与运行截图
一开始确实不知道这个实验怎么下手。
要用Java编写?
服务器与客户端都用Java编写?
客户端直接用浏览器?
浏览器发送的请求是怎么样的?
浏览器那的地址栏要怎么写?
......
诸如此类的一大堆问题,让这个实验貌似不那么可爱..其实一个个解决下来的话,发现这个实验还是挺有趣的。
其实有时候一下子就能做出来的东西反而没那么有趣了,男人不都喜欢“犹抱琵琶半遮面”嘛....还是有点儿道理的。
解决问题还是从外到内比较好,层层深入。
一
第一个问题,客户端与服务端的角色各是由什么来扮演?
我想服务器端铁定是Java没跑的,关键是客户端。
要求里说的web浏览器貌似暗示我用浏览器作为客户端,可是是否可以用java模拟一个简陋版的浏览器呢?
...事实证明我想多了.....
二
既然客户端是浏览器,那么要把整个过程弄清楚:
我理解的过程是:
我们在浏览器那里输入地址,按下回车之后浏览器进程就往相应地址的服务器的相应端口上发送请求。
至于怎么把浏览器网址栏里的信息转化成HTTP请求,那是浏览器的事儿了。
然后我自己用Java编写的服务器只需提前在对应的端口那监听着,接收web浏览器发过来的请求。
再根据请求内容作相应的逻辑处理,把web浏览器请求的文件通过Socket对象的流发送过去即可。
(做这个实验的时候才真正理解了HTTP协议原来只针对文件传输的这一句话)
三
上面的过程弄懂后,我就写了个demo,复用了之前lab1文件传输的代码,把所需传输的文件指定(如StringfilePath="D:
\\temp\\index.html";)然后开浏览器(实验中用的360浏览器),输入本机地址以及端口号(127.0.0.1:
8080)。
激动人心的时候到了...==回车..看到了自己写的那个巨丑的html文件,嗯。
验证了猜测是正确的。
四
接下来要实现在浏览器地址那指定获取的页面(127.0.0.1:
8080/aotherpage.html)。
要实现这个,服务器首先要从Socket流里把web浏览器的请求给读出来,然后从请求中把文件的路径给提取出来,再转化为特定的格式。
然后用lab1中文件流读取的方法,把文件传给web浏览器。
首先是读请求,这个很简单,一个readline()就可以了(实验原因,请求头下面的请求信息不予读取,以简化程序,若需读取,while循环里readline()读完即可)。
读出来的是类似“GET/index.htmlHTTP/1.1”之类的字符串。
接下来要把文件路径给提取出来,也就是“/index.html”。
我写了个简单的函数,思想就是用flag标记两个''字符,然后取子字符串,一个substring就可以解决。
最后是转换了,要把路径里的/都转换成\\,因为windows里的路径都是用\,加上转义符就是\\。
同样写了个函数,解决了。
注:
substring(a,b)是截取下标a到b-1
然后再一次在浏览器那输入127.0.0.1:
8080/index.html 额,激动人心的时候又到了...==回车..再次看到那巨丑的html文件,一切顺利。
五
接着要弄CGI了,关于CGI,在做这个实验前其实我没有真正的弄懂,就表面上理解而已。
书上说:
CGI协议在信息服务器和外部进程之间,提供了一个接口或网关。
好吧,书上的定义通常都是越看越不清楚的。
不过这里有个很重要的信息,就是CGI是一个协议。
而实现了这个协议的程序叫CGI程序或CGI应用。
也就是说,浏览器那里请求一个CGI的话,是请求我们的服务器运行CGI程序(exe之类的),再把那个程序的输出流赋值给文件传输的输入流,具体代码如下:
cgiIn = new DataInputStream(CGI.getInputStream());
cgiOut = new DataOutputStream(myDataSocket.getOutputStream());
其中CGI是CGI应用进程的句柄。
至于为什么CGI调用的是getInputStream(),这需要理解Input在这里是相对于什么input。
可以拿Socket的缓冲流类比,Socket的getInputStream()是从流读到程序内存中。
所以这个input是对运行中的程序来说的。
于是CGI调用的getInputStream()就是把CGI的输出作为程序的input。
关于CGI的另外一行重要代码如下:
Process CGI=run.exec(wholeFilePath.replaceAll(".cgi", ".exe"));
这里的重点是吧请求里的文件路径的后缀改了。
(这一行代码在工程中应该放在上面那两行的前面,由于这样比较符合思维的走势,就放在这里了)
好了激动人心的时刻终于又要来一次了...浏览器那输入127.0.0.1:
8080/test.cgi回车....OK.看到了exe里面的输出(其实就是一些Html语句)显示在浏览器上了。
六
接下来实现post请求CGI。
要浏览器发出POST请求,我们首先要弄一个带表单的html页面,我自己的是:
Post Page
This is the post page.
这样,在这个页面中,点击here这个button就会向服务器发出一个“POST/postpage.cgiHTTP1.1”请求。
然后实验,点button,再次看到了exe程序里的输出,同上面那个“激动人心的时刻”,不过上面那个是GET请求。
至此实验做完鸟。
实验代码:
importjava.io.BufferedReader;
importjava.io.DataInputStream;
importjava.io.DataOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.InputStreamReader;
importjava.io.ObjectInputStream.GetField;
import.ServerSocket;
import.Socket;
publicclassMyServer{
publicstaticvoidmain(String[]args){
//定义服务器html,cgi文件存放目录
StringfilePath="D:
\\workspace\\lab2\\htmlfiles";
//定义服务器端口
intport=8080;
try{
ServerSocketmyConSocket=newServerSocket(port);
while(true){
//等待客户连接
System.out.println("Waitingtobeconnected.");
SocketmyDataSocket=myConSocket.accept();
System.out.println("Alreadygettheapply.");
//定义输入输出流对象
InputStreamis=myDataSocket.getInputStream();
BufferedReaderbr=newBufferedReader(newInputStreamReader(is));
DataInputStreamcgiIn=null;
DataOutputStreamcgiOut=null;
DataInputStreamfileIn=null;
DataOutputStreamfileOut=null;
//分析请求中的路径名,如无指定则返回index.html
Stringtemp=br.readLine();
System.out.println("Therequestis:
"+temp);
Stringjudgefornull=getPathSlashOK(getFilePath(temp));
System.out.println("Therealsub-pathis:
"+judgefornull);
if(judgefornull.equalsIgnoreCase("\\"))
judgefornull="\\index.html";
//判断页面类型
StringfileType;
intlength=judgefornull.length();
if(length>4){
StringtestForHtml=judgefornull.substring(length-4,length);
StringtestForCgi=judgefornull.substring(length-3,length);
if(testForHtml.equalsIgnoreCase("html"))
fileType="html";
elseif(testForCgi.equalsIgnoreCase("cgi"))
fileType="cgi";
else
fileType="unknown";
}
else
fileType="unknown";
//得到输出总路径
StringwholeFilePath=filePath+judgefornull;
System.out.println("wholepathis:
"+wholeFilePath);
if(fileType.equals("cgi")){
//CGI
System.out.println("Ishoulduseacgi.");
Runtimerun=Runtime.getRuntime();
try{
ProcessCGI=run.exec(wholeFilePath.replaceAll(".cgi",".exe"));
cgiIn=newDataInputStream(CGI.getInputStream());
cgiOut=newDataOutputStream(myDataSocket.getOutputStream());
//定义缓冲区
intbufferSize=8192;
byte[]buff=newbyte[bufferSize];
//从文件流读入byte数组,再输出到DataSocket的缓冲流里
while(true)
{
intread=0;
if(cgiIn!
=null)
read=cgiIn.read(buff);
if(read==-1)
break;
cgiOut.write(buff,0,read);
}
//冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
cgiOut.flush();
cgiIn.close();
cgiOut.close();
}catch(IOExceptione){
//处理当被请求的cgi程序不存在时的状况,返回notfound.html
wholeFilePath="D:
\\workspace\\lab2\\htmlfiles\\notfound.html";
fileIn=newDataInputStream(newFileInputStream(wholeFilePath));
fileOut=newDataOutputStream(myDataSocket.getOutputStream());
//创建File变量,获取文件长度
Filefi=newFile(wholeFilePath);
System.out.println("文件长度:
"+(int)fi.length());
//定义缓冲区
intbufferSize=8192;
byte[]buff=newbyte[bufferSize];
//从文件流读入byte数组,再输出到DataSocket的缓冲流里
while(true)
{
intread=0;
if(fileIn!
=null)
read=fileIn.read(buff);
if(read==-1)
break;
fileOut.write(buff,0,read);
}
//冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
fileOut.flush();
fileIn.close();
fileOut.close();
}
}
elseif(fileType.equals("html")){
//html
//创建File变量
Filefi=newFile(wholeFilePath);
//若所请求的html文件不存在,则返回notfound.html文件
if(!
fi.exists())
wholeFilePath="D:
\\workspace\\lab2\\htmlfiles\\notfound.html";
fi=newFile(wholeFilePath);
System.out.println("文件长度:
"+(int)fi.length());
fileIn=newDataInputStream(newFileInputStream(wholeFilePath));
fileOut=newDataOutputStream(myDataSocket.getOutputStream());
//定义缓冲区
intbufferSize=8192;
byte[]buff=newbyte[bufferSize];
//从文件流读入byte数组,再输出到DataSocket缓冲流里
while(true)
{
intread=0;
if(fileIn!
=null)
read=fileIn.read(buff);
if(read==-1)
break;
fileOut.write(buff,0,read);
}
//冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
fileOut.flush();
fileIn.close();
fileOut.close();
}
else{
//otherpages
wholeFilePath="D:
\\workspace\\lab2\\htmlfiles\\notfound.html";
fileIn=newDataInputStream(newFileInputStream(wholeFilePath));
fileOut=newDataOutputStream(myDataSocket.getOutputStream());
//创建File变量,获取文件长度,这个有问题,等会位置要换一下
Filefi=newFile(wholeFilePath);
System.out.println("文件长度:
"+(int)fi.length());
//定义缓冲区
intbufferSize=8192;
byte[]buff=newbyte[bufferSize];
//从文件流读入byte数组,再输出到DataSocket缓冲流里
while(true)
{
intread=0;
if(fileIn!
=null)
read=fileIn.read(buff);
if(read==-1)
break;
fileOut.write(buff,0,read);
}
//冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
fileOut.flush();
fileIn.close();
fileOut.close();
}
System.out.println("filehasbeensent");
myDataSocket.close();
}
}catch(IOExceptione){
System.err.println(e.getMessage());
}
}
//从请求中提取所需文件路径
publicstaticStringgetFilePath(Stringapply){
intflag1=0;
intflag2=0;
for(inti=0;iif(apply.charAt(i)==''){
flag1=i;
break;
}
}
for(inti=flag1+1;iif(apply.charAt(i)==''){
flag2=i;
break;
}
}
Stringpath=apply.substring(flag1+1,flag2);
returnpath;
}
//把请求中的"/"换成"\\"
publicstaticStringgetPathSlashOK(StringrawPat