博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分布式计算 lab3 HTTP服务器
阅读量:4606 次
发布时间:2019-06-09

本文共 13662 字,大约阅读时间需要 45 分钟。

题目:

 

实验三:实现一个基本的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  2      3         Post Page 4      5  6      7         

This is the post page.

8
9

10

Press to test for the post method.

11
12 13

这样,在这个页面中,点击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.html110                         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                     //html144                     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 pages184                     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

 

 

 

 

 

 

转载于:https://www.cnblogs.com/HenryThinker/archive/2012/12/31/2840754.html

你可能感兴趣的文章
-Dmaven.multiModuleProjectDirectory system propery is not set.
查看>>
Python2 unichr() 函数
查看>>
Python 字典 copy()方法
查看>>
Minimum Path Sum
查看>>
Remove Duplicates from Sorted Array II
查看>>
常量指针和指针常量巧妙记忆方法[转]
查看>>
python-haproxy作业讲解视频总结
查看>>
mui搜索框 搜索点击事件
查看>>
select2 下拉搜索控件
查看>>
WebAPI常见的鉴权方法,及其适用范围
查看>>
08. 删除重复&海量数据
查看>>
重新想象 Windows 8 Store Apps (71) - 其它: C# 调用 C++
查看>>
发布mvc遇到的HTTP错误 403.14-Forbidden解决办法
查看>>
记录一些好用的工具
查看>>
超链接样式设置(去下划线)(转)
查看>>
2016012003+陈琦+散列函数的应用及其安全性
查看>>
Android 状态栏通知Notification、NotificationManager详解
查看>>
UIApplicationDelegate协议
查看>>
Jmeter测试dubbo接口填坑
查看>>
[zz]GDB调试精粹及使用实例
查看>>