分布式计算 lab3 HTTP服务器

题目:

 

实验三:实现一个基本的Web服务器程序

 

【实验目的及要求】

采用Socket API知识和对HTTP协议,CGI的理解,实现一个基本的WEB服务器程序,要求服务器能成功响应客户程序发来的GET命令(传送文件),进一步实现响应POST和GET命令的CGI程序调用请求。


要求:要求独立完成。

 


【实验原理和步骤】


1.实验原理
(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头信息:

HTTP 1.0 200 OK
WEBServer:1.0 // 服务器类型
content_type:类型
content_length:长度值

(5)响应POST和GET命令的CGI程序调用请求需要服务器执行外部程序,Java执行外部可执行程序的方法是:首先通过Runtime run = Runtime.getRuntime()返回与当前Java 应用程序相关的运行时对象;然后调用Process CGI = run.exec(ProgramName)另启一个进程来执行一个外部可执行程序。


2. 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浏览器,(此处只能是静态的CGI程序,因为本设计不涉及传递环境变量)然后关闭文件。否则发送错误信息给Web浏览器;

(7) 关闭与相应Web浏览器连接的socket字。

 

 

 

一开始确实不知道这个实验怎么下手。要用Java编写?服务器与客户端都用Java编写?客户端直接用浏览器?浏览器发送的请求是怎么样的?浏览器那的地址栏要怎么写?......

诸如此类的一大堆问题,让这个实验貌似不那么可爱..其实一个个解决下来的话,发现这个实验还是挺有趣的。

其实有时候一下子就能做出来的东西反而没那么有趣了,男人不都喜欢“犹抱琵琶半遮面”嘛....还是有点儿道理的。

 

解决问题还是从外到内比较好,层层深入。

 

第一个问题,客户端与服务端的角色各是由什么来扮演?我想服务器端铁定是Java没跑的,关键是客户端。要求里说的web浏览器貌似暗示我用浏览器作为客户端,可是是否可以用java模拟一个简陋版的浏览器呢?...事实证明我想多了.....

 

既然客户端是浏览器,那么要把整个过程弄清楚:

我理解的过程是:我们在浏览器那里输入地址,按下回车之后浏览器进程就往相应地址的服务器的相应端口上发送请求。至于怎么把浏览器网址栏里的信息转化成HTTP请求,那是浏览器的事儿了。然后我自己用Java编写的服务器只需提前在对应的端口那监听着,接收web浏览器发过来的请求。再根据请求内容作相应的逻辑处理,把web浏览器请求的文件通过Socket对象的流发送过去即可。(做这个实验的时候才真正理解了HTTP协议原来只针对文件传输的这一句话)

 

上面的过程弄懂后,我就写了个demo,复用了之前lab1文件传输的代码,把所需传输的文件指定(如String filePath = "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.html HTTP/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页面,我自己的是:

 1 <html>
 2     <head>
 3         <title>Post Page</title>
 4     <head>
 5 
 6     <body>
 7         <h1>This is the post page.</h1>
 8         <form method="post" action="test.cgi">
 9             <hr>
10             <p>Press <input type="submit" value="here"> to test for the post method.</p>
11         </form>
12     </body>
13 </html>

这样,在这个页面中,点击here这个button就会向服务器发出一个“POST /postpage.cgi HTTP1.1”请求。

然后实验,点button,再次看到了exe程序里的输出,同上面那个“激动人心的时刻”,不过上面那个是GET请求。

 

至此实验做完鸟。

 

还有一些问题:

1. 用Chrome浏览器浏览器的时候,除了我们所预想的请求外,还会发一个对favicon.ico的请求,不确定这个到底有什么用。

2. 360浏览器有时候请求到了页面,可是在自己编写的服务器那里没有请求被接收的记录。这证明浏览器实际上没有请求,估计360弄了个cache,把一些最近请求的页面放进去了,以便快速恢复。可是如果此页面更新频繁怎么办,我们都需要的是最新的页面?cache此时反而变成了个累赘....

 

实验代码:

 

  1 import java.io.BufferedReader;
  2 import java.io.DataInputStream;
  3 import java.io.DataOutputStream;
  4 import java.io.File;
  5 import java.io.FileInputStream;
  6 import java.io.IOException;
  7 import java.io.InputStream;
  8 import java.io.InputStreamReader;
  9 import java.io.ObjectInputStream.GetField;
 10 import java.net.ServerSocket;
 11 import java.net.Socket;
 12 
 13 
 14 public class MyServer {
 15 
 16     public static void main(String[] args){
 17         //定义服务器html,cgi文件存放目录
 18         String filePath = "D:\\workspace\\lab3\\htmlfiles";
 19         
 20         //定义服务器端口
 21         int port = 8080;
 22         
 23         try {
 24             
 25             ServerSocket myConSocket = new ServerSocket(port);
 26             
 27             while(true){
 28                 //等待客户连接
 29                 System.out.println("Waiting to be connected.");
 30                 Socket myDataSocket = myConSocket.accept();
 31                 System.out.println("Already get the apply.");
 32                 
 33                 //定义输入输出流对象
 34                 InputStream is =  myDataSocket.getInputStream();
 35                 BufferedReader br = new BufferedReader(new InputStreamReader(is));
 36                 DataInputStream cgiIn = null;
 37                 DataOutputStream cgiOut = null;
 38                 DataInputStream fileIn = null;
 39                 DataOutputStream fileOut = null;
 40                 
 41                 //分析请求中的路径名,如无指定则返回index.html
 42                 String temp = br.readLine();
 43          
 44                 System.out.println("The request is: " + temp);
 45                 String judgefornull = getPathSlashOK(getFilePath(temp));
 46                 System.out.println("The real sub-path is: " + judgefornull);
 47                 if(judgefornull.equalsIgnoreCase("\\"))
 48                     judgefornull = "\\index.html";
 49                 
 50                 //判断页面类型
 51                 String fileType;
 52                 int length = judgefornull.length();
 53                 if(length > 4){
 54                     String testForHtml = judgefornull.substring(length-4, length);
 55                     String testForCgi = judgefornull.substring(length-3, length);
 56                     if(testForHtml.equalsIgnoreCase("html"))
 57                         fileType = "html";
 58                     else if(testForCgi.equalsIgnoreCase("cgi"))
 59                         fileType = "cgi";
 60                     else
 61                         fileType = "unknown";
 62                 }
 63                 else
 64                     fileType = "unknown";
 65                 
 66                 
 67                 
 68 
 69                 //得到输出总路径
 70                 String wholeFilePath = filePath + judgefornull;
 71                 System.out.println("whole path is: " + wholeFilePath);
 72                 
 73                 if(fileType.equals("cgi")){
 74                     //CGI
 75                     System.out.println("I should use a cgi.");
 76                     Runtime run = Runtime.getRuntime();                    
 77                     
 78                     try {
 79                         Process CGI=run.exec(wholeFilePath.replaceAll(".cgi", ".exe"));
 80                         
 81                         cgiIn = new DataInputStream(CGI.getInputStream());
 82                         cgiOut = new DataOutputStream(myDataSocket.getOutputStream());
 83                         
 84                         //定义缓冲区
 85                         int bufferSize = 8192;
 86                         byte[] buff = new byte[bufferSize];
 87                         
 88                         //从文件流读入byte数组,再输出到Data Socket的缓冲流里
 89                         while(true)
 90                         {
 91                             int read = 0;
 92                             if(cgiIn != null)
 93                                 read = cgiIn.read(buff);
 94                             
 95                             if(read == -1)
 96                                 break;
 97                             
 98                             cgiOut.write(buff,0,read);
 99                         }
100                         
101                         //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
102                         cgiOut.flush();
103                         
104                         cgiIn.close();
105                         cgiOut.close();
106                         
107                         
108                     } catch (IOException e) {
109                         //处理当被请求的cgi程序不存在时的状况,返回notfound.html
110                         wholeFilePath = "D:\\workspace\\lab3\\htmlfiles\\notfound.html";
111                         fileIn = new DataInputStream(new FileInputStream(wholeFilePath));
112                         fileOut = new DataOutputStream(myDataSocket.getOutputStream());
113 
114                         //创建File变量,获取文件长度
115                         File fi = new File(wholeFilePath);
116                         System.out.println("文件长度: " + (int)fi.length());
117                         
118                         //定义缓冲区
119                         int bufferSize = 8192;
120                         byte[] buff = new byte[bufferSize];
121                         
122                         //从文件流读入byte数组,再输出到Data Socket的缓冲流里
123                         while(true)
124                         {
125                             int read = 0;
126                             if(fileIn != null)
127                                 read = fileIn.read(buff);
128                             
129                             if(read == -1)
130                                 break;
131                             
132                             fileOut.write(buff,0,read);
133                         }
134                         
135                         //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
136                         fileOut.flush();
137                         
138                         fileIn.close();
139                         fileOut.close();
140                     }
141                 }
142                 else if(fileType.equals("html")){
143                     //html
144                     
145                     //创建File变量
146                     File fi = new File(wholeFilePath);                    
147                     
148                     //若所请求的html文件不存在,则返回notfound.html文件
149                     if(!fi.exists())
150                         wholeFilePath = "D:\\workspace\\lab3\\htmlfiles\\notfound.html";
151                     
152                     fi = new File(wholeFilePath);
153                     System.out.println("文件长度: " + (int)fi.length());
154                     
155                     fileIn = new DataInputStream(new FileInputStream(wholeFilePath));
156                     fileOut = new DataOutputStream(myDataSocket.getOutputStream());
157                     
158                     //定义缓冲区
159                     int bufferSize = 8192;
160                     byte[] buff = new byte[bufferSize];
161                     
162                     //从文件流读入byte数组,再输出到Data Socket的缓冲流里
163                     while(true)
164                     {
165                         int read = 0;
166                         if(fileIn != null)
167                             read = fileIn.read(buff);
168                         
169                         if(read == -1)
170                             break;
171                         
172                         fileOut.write(buff,0,read);
173                     }
174                     
175                     //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
176                     fileOut.flush();
177                     
178                     fileIn.close();
179                     fileOut.close();
180                     
181                 }
182                 else{
183                     //other pages
184                     
185                     wholeFilePath = "D:\\workspace\\lab3\\htmlfiles\\notfound.html";
186                     fileIn = new DataInputStream(new FileInputStream(wholeFilePath));
187                     fileOut = new DataOutputStream(myDataSocket.getOutputStream());
188 
189                     //创建File变量,获取文件长度,这个有问题,等会位置要换一下
190                     File fi = new File(wholeFilePath);
191                     System.out.println("文件长度: " + (int)fi.length());
192                     
193                     //定义缓冲区
194                     int bufferSize = 8192;
195                     byte[] buff = new byte[bufferSize];
196                     
197                     //从文件流读入byte数组,再输出到Data Socket的缓冲流里
198                     while(true)
199                     {
200                         int read = 0;
201                         if(fileIn != null)
202                             read = fileIn.read(buff);
203                         
204                         if(read == -1)
205                             break;
206                         
207                         fileOut.write(buff,0,read);
208                     }
209                     
210                     //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭
211                     fileOut.flush();
212                     
213                     fileIn.close();
214                     fileOut.close();
215                 }
216                 
217                 System.out.println("file has been sent");
218                 
219                 myDataSocket.close();
220             }
221             
222         } catch (IOException e) {
223             System.err.println(e.getMessage());
224         }
225         
226         
227         
228     }
229     
230     //从请求中提取所需文件路径
231     public static String getFilePath(String apply){
232         int flag1 = 0;
233         int flag2 = 0;
234         for(int i = 0;i<apply.length();i++){
235             if(apply.charAt(i) == ' '){
236                 flag1 = i;
237                 break;
238             }
239         }
240         for(int i = flag1 + 1;i<apply.length();i++){
241             if(apply.charAt(i) == ' '){
242                 flag2 = i;
243                 break;
244             }
245                  
246         }
247         String path = apply.substring(flag1+1, flag2);
248         return path;
249     }
250     
251     //把请求中的"/"换成"\\"
252     public static String getPathSlashOK(String rawPath){
253         String cookedPath = "";
254         int i = 0;
255         //若传"/"进来,即无指定路径,则直接赋值"\\"
256         int flag = rawPath.length();
257         while(true){
258             if(flag == 1){
259                 rawPath = "\\";
260                 break;
261             }
262             if(i == rawPath.length())
263                 break;
264             if(rawPath.charAt(i) == '/'){
265                 rawPath = rawPath.substring(0, i) + "\\" + rawPath.substring(i+1, rawPath.length());
266                 i += 2;
267             }
268             else
269                 i++;                
270         }
271         
272         cookedPath = rawPath;
273         
274         return cookedPath;
275     }
276     
277     
278 }

 

 

 

 

 

 

posted @ 2012-12-31 16:25  CheckMate  阅读(1110)  评论(0编辑  收藏  举报