1、昆明理工大学 WEB服务与分布式计算 实验二昆明理工大学信息工程与自动化学院学生实验报告( 2014 2015 学年 第 二 学期 )课程名称:Web服务与分布计算 实验室:信自楼234 2015年4月28日专业、年级、班计科122班学号201210405204姓名邹华宇成绩实验项目名称实验二 实现一个基本的WEB服务器程序指导教师王红斌教师评语该同学是否了解实验原理: A.了解 B.基本了解 C.不了解该同学的实验能力: A.强 B.中等 C.差 该同学的实验是否达到要求: A.达到 B.基本达到 C.未达到实验报告是否规范: A.规范 B.基本规范 C.不规范实验过程是否详细记录: A.详
2、细 B.一般 C.没有 教师签名: 年 月 日一、实验目的熟悉Socket通讯原理和理解HTTP协议,了解WEB服务器的工作原理。二、实验要求采用Socket API知识和对HTTP协议,CGI(Common Gateway Interface,通用网关界面)的理解,实现一个基本的WEB服务器程序,要求服务器能成功响应客户程序发来的GET命令(传输文件),进一步实现响应POST和GET命令的CGI程序用请求。三、实验原理与步骤(1)服务器主要监听来至客户浏览器或是客户端程序的连接请求,并且接受到客户请求后对客户请求作出响应。如果请求时静态的文本或是网页则将内容发送给客户。如何是CGI程序则服务
3、器调用请求的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服务器首
4、先传送一些HTTP头信息:HTTP 1.0 200 OKWEBServer:1.0content_type:类型content_length:长度值(5)响应POST和GET命令的CGI程序调用请求需要服务器执行外部程序,Java执行外部可执行程序的方法是:首先通过Runtime run=Runtime.getRuntime()返回与当前Java应用程序相关的运行时对象;然后调用Process CGI = run.exec ( ProgramName ) 另启一个进程来执行一个外部可执行程序。四、Web服务器的实现步骤(1)创建ServerSocket类对象,监听端口8080。这是为了区别与H
5、TTP的标准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浏览器的
6、socket字。五、结果分析与运行截图一开始确实不知道这个实验怎么下手。要用Java编写?服务器与客户端都用Java编写?客户端直接用浏览器?浏览器发送的请求是怎么样的?浏览器那的地址栏要怎么写?.诸如此类的一大堆问题,让这个实验貌似不那么可爱.其实一个个解决下来的话,发现这个实验还是挺有趣的。其实有时候一下子就能做出来的东西反而没那么有趣了,男人不都喜欢“犹抱琵琶半遮面”嘛.还是有点儿道理的。解决问题还是从外到内比较好,层层深入。一第一个问题,客户端与服务端的角色各是由什么来扮演?我想服务器端铁定是Java没跑的,关键是客户端。要求里说的web浏览器貌似暗示我用浏览器作为客户端,可是是否可以
7、用java模拟一个简陋版的浏览器呢?.事实证明我想多了.二既然客户端是浏览器,那么要把整个过程弄清楚:我理解的过程是:我们在浏览器那里输入地址,按下回车之后浏览器进程就往相应地址的服务器的相应端口上发送请求。至于怎么把浏览器网址栏里的信息转化成HTTP请求,那是浏览器的事儿了。然后我自己用Java编写的服务器只需提前在对应的端口那监听着,接收web浏览器发过来的请求。再根据请求内容作相应的逻辑处理,把web浏览器请求的文件通过Socket对象的流发送过去即可。(做这个实验的时候才真正理解了HTTP协议原来只针对文件传输的这一句话)三上面的过程弄懂后,我就写了个demo,复用了之前lab1文件传
8、输的代码,把所需传输的文件指定(如String filePath = D:tempindex.html;)然后开浏览器(实验中用的360浏览器),输入本机地址以及端口号(127.0.0.1:8080)。激动人心的时候到了.= =回车.看到了自己写的那个巨丑的html文件,嗯。验证了猜测是正确的。四接下来要实现在浏览器地址那指定获取的页面(127.0.0.1:8080/aotherpage.html)。要实现这个,服务器首先要从Socket流里把web浏览器的请求给读出来,然后从请求中把文件的路径给提取出来,再转化为特定的格式。然后用lab1中文件流读取的方法,把文件传给web浏览器。首先是读请
9、求,这个很简单,一个readline()就可以了(实验原因,请求头下面的请求信息不予读取,以简化程序,若需读取,while循环里readline()读完即可)。读出来的是类似“GET /index.html HTTP/1.1”之类的字符串。接下来要把文件路径给提取出来,也就是“/index.html”。我写了个简单的函数,思想就是用flag标记两个 字符,然后取子字符串,一个substring就可以解决。最后是转换了,要把路径里的/都转换成,因为windows里的路径都是用,加上转义符就是。同样写了个函数,解决了。注:substring(a,b)是截取下标a到b-1然后再一次在浏览器那输入12
10、7.0.0.1:8080/index.html 额,激动人心的时候又到了.= =回车.再次看到那巨丑的html文件,一切顺利。五接着要弄CGI了,关于CGI,在做这个实验前其实我没有真正的弄懂,就表面上理解而已。书上说:CGI协议在信息服务器和外部进程之间,提供了一个接口或网关。好吧,书上的定义通常都是越看越不清楚的。不过这里有个很重要的信息,就是CGI是一个协议。而实现了这个协议的程序叫CGI程序或CGI应用。也就是说,浏览器那里请求一个CGI的话,是请求我们的服务器运行CGI程序(exe之类的),再把那个程序的输出流赋值给文件传输的输入流,具体代码如下:cgiIn=newDataInput
11、Stream(CGI.getInputStream();cgiOut=newDataOutputStream(myDataSocket.getOutputStream();其中CGI是CGI应用进程的句柄。至于为什么CGI调用的是getInputStream(),这需要理解Input在这里是相对于什么input。可以拿Socket的缓冲流类比,Socket的getInputStream()是从流读到程序内存中。所以这个input是对运行中的程序来说的。于是CGI调用的getInputStream()就是把CGI的输出作为程序的input。关于CGI的另外一行重要代码如下:ProcessCGI=
12、run.exec(wholeFilePath.replaceAll(.cgi,.exe);这里的重点是吧请求里的文件路径的后缀改了。(这一行代码在工程中应该放在上面那两行的前面,由于这样比较符合思维的走势,就放在这里了)好了激动人心的时刻终于又要来一次了.浏览器那输入127.0.0.1:8080/test.cgi 回车.OK.看到了exe里面的输出(其实就是一些Html语句)显示在浏览器上了。六接下来实现post请求CGI。要浏览器发出POST请求,我们首先要弄一个带表单的html页面,我自己的是:PostPageThisisthepostpage.Presstotestforthepostm
13、ethod.这样,在这个页面中,点击here这个button就会向服务器发出一个“POST /postpage.cgi HTTP1.1”请求。然后实验,点button,再次看到了exe程序里的输出,同上面那个“激动人心的时刻”,不过上面那个是GET请求。至此实验做完鸟。实验代码:import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java
14、.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream.GetField; import .ServerSocket; import .Socket; public class MyServer public static void main(String args) /定义服务器html,cgi文件存放目录 String filePath = D:workspacelab2htmlfiles; /定义服务器端口 int por
15、t = 8080; try ServerSocket myConSocket = new ServerSocket(port); while(true) /等待客户连接 System.out.println(Waiting to be connected.); Socket myDataSocket = myConSocket.accept(); System.out.println(Already get the apply.); /定义输入输出流对象 InputStream is = myDataSocket.getInputStream(); BufferedReader br = ne
16、w BufferedReader(new InputStreamReader(is); DataInputStream cgiIn = null; DataOutputStream cgiOut = null; DataInputStream fileIn = null; DataOutputStream fileOut = null; /分析请求中的路径名,如无指定则返回index.html String temp = br.readLine(); System.out.println(The request is: + temp); String judgefornull = getPat
17、hSlashOK(getFilePath(temp); System.out.println(The real sub-path is: + judgefornull); if(judgefornull.equalsIgnoreCase() judgefornull = index.html; /判断页面类型 String fileType; int length = judgefornull.length(); if(length 4) String testForHtml = judgefornull.substring(length-4, length); String testForC
18、gi = judgefornull.substring(length-3, length); if(testForHtml.equalsIgnoreCase(html) fileType = html; else if(testForCgi.equalsIgnoreCase(cgi) fileType = cgi; else fileType = unknown; else fileType = unknown; /得到输出总路径 String wholeFilePath = filePath + judgefornull; System.out.println(whole path is:
19、+ wholeFilePath); if(fileType.equals(cgi) /CGI System.out.println(I should use a cgi.); Runtime run = Runtime.getRuntime(); try Process CGI=run.exec(wholeFilePath.replaceAll(.cgi, .exe); cgiIn = new DataInputStream(CGI.getInputStream(); cgiOut = new DataOutputStream(myDataSocket.getOutputStream(); /
20、定义缓冲区 int bufferSize = 8192; byte buff = new bytebufferSize; /从文件流读入byte数组,再输出到Data Socket的缓冲流里 while(true) int read = 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 (I
21、OException e) /处理当被请求的cgi程序不存在时的状况,返回notfound.html wholeFilePath = D:workspacelab2htmlfilesnotfound.html; fileIn = new DataInputStream(new FileInputStream(wholeFilePath); fileOut = new DataOutputStream(myDataSocket.getOutputStream(); /创建File变量,获取文件长度 File fi = new File(wholeFilePath); System.out.pri
22、ntln(文件长度: + (int)fi.length(); /定义缓冲区 int bufferSize = 8192; byte buff = new bytebufferSize; /从文件流读入byte数组,再输出到Data Socket的缓冲流里 while(true) int read = 0; if(fileIn != null) read = fileIn.read(buff); if(read = -1) break; fileOut.write(buff,0,read); /冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 fileOut.flush(); fil
23、eIn.close(); fileOut.close(); else if(fileType.equals(html) /html /创建File变量 File fi = new File(wholeFilePath); /若所请求的html文件不存在,则返回notfound.html文件 if(!fi.exists() wholeFilePath = D:workspacelab2htmlfilesnotfound.html; fi = new File(wholeFilePath); System.out.println(文件长度: + (int)fi.length(); fileIn =
24、 new DataInputStream(new FileInputStream(wholeFilePath); fileOut = new DataOutputStream(myDataSocket.getOutputStream(); /定义缓冲区 int bufferSize = 8192; byte buff = new bytebufferSize; /从文件流读入byte数组,再输出到Data Socket缓冲流里 while(true) int read = 0; if(fileIn != null) read = fileIn.read(buff); if(read = -1)
25、 break; fileOut.write(buff,0,read); /冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 fileOut.flush(); fileIn.close(); fileOut.close(); else /other pages wholeFilePath = D:workspacelab2htmlfilesnotfound.html; fileIn = new DataInputStream(new FileInputStream(wholeFilePath); fileOut = new DataOutputStream(myDataSocket.
26、getOutputStream(); /创建File变量,获取文件长度,这个有问题,等会位置要换一下 File fi = new File(wholeFilePath); System.out.println(文件长度: + (int)fi.length(); /定义缓冲区 int bufferSize = 8192; byte buff = new bytebufferSize; /从文件流读入byte数组,再输出到Data Socket缓冲流里 while(true) int read = 0; if(fileIn != null) read = fileIn.read(buff); if
27、(read = -1) break; fileOut.write(buff,0,read); /冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 fileOut.flush(); fileIn.close(); fileOut.close(); System.out.println(file has been sent); myDataSocket.close(); catch (IOException e) System.err.println(e.getMessage(); /从请求中提取所需文件路径 public static String getFilePath(Strin
28、g apply) int flag1 = 0; int flag2 = 0; for(int i = 0;iapply.length();i+) if(apply.charAt(i) = ) flag1 = i; break; for(int i = flag1 + 1;iapply.length();i+) if(apply.charAt(i) = ) flag2 = i; break; String path = apply.substring(flag1+1, flag2); return path; /把请求中的/换成 public static String getPathSlashOK(String rawPat