在 Java 中实现 Web 服务器
一篇关于如何在 Java 中实现功能性 Web 服务器的文章
什么是 Web 服务器?
Web 服务器是负责接收浏览器请求、检索指定文件(或执行指定的 CGI、PHP、ASP 脚本)并返回其内容(或脚本结果)的软件。如今,互联网上大多数 Web 服务器运行在 UNIX 机器上,尽管其他平台(如 Windows 95、Windows NT 和 Macintosh)上的服务器比例正在稳步增长。
Web 服务器首先通过*套接字*(一种通过网络通信的机制)检索请求。Web 服务器监听服务器机器上的特定端口,通常是端口 80。默认情况下,Web 浏览器使用端口 80 进行请求。
一旦服务器收到请求,它就会定位所请求的文档。它在*文档根目录*下查找文件。例如,如果根目录是*C:\AssWebSrv\htdocs*,而客户端请求文档* /public/docs.html*,则服务器将检索*C:\AssWebSrv\htdocs\public\docs.html*。
如果 URL 没有指定文件而只是指定了一个目录,服务器将返回*目录索引*。
服务器将文件内容连同一些*HTTP 响应头*一起发送回客户端。响应头中的数据包括*媒体类型*(也称为*内容类型*或*MIME 类型*),即文件的格式。确定格式的方式取决于服务器,但通常来自文档的后缀:*.html*被认为是 HTML 文档,*.pdf*被假定为 Adobe Acrobat 文档,依此类推。
Web 服务器主要有四种类型:
- NCSA 服务器,由伊利诺伊大学厄巴纳-香槟分校的超级计算应用国家中心维护。
- Apache,NCSA 的一个变体,已发展成为当今最受欢迎的 Web 服务器。
- CERN 服务器,由万维网联盟维护。
- Netscape 系列服务器,尽管 Netscape 服务器是商业产品。
AssoudiWebServer1.5 机制如下:
既然我们已经对 Web 服务器有了很好的描述,现在让我们开始探索 AssoudiWebServer1.5 及其机制。
AssoudiWebServer1.5 是一个完全用 Java 开发的 Web 服务器,而几乎所有 Web 服务器(如 Apache Web 服务器)都是用 C 语言开发的。
客户端(通常是浏览器)向服务器发出 HTTP 请求时,会为每个打开的文件打开许多套接字。例如,一个包含 10 张图片的 HTML 文件,服务器必须创建 10 个套接字来处理每个文件。所以您可能会问服务器如何同时处理许多文件。答案自然是使用线程。对于每个接受的请求,服务器必须创建一个新线程,该线程处理请求然后发送结果。
//
try{
//creating a new ServerSocket Listing on SERVER_PORT
ServerSocket S=new ServerSocket(SERVER_PORT);
try{
while(true){
//returning an established socket via the ServerSocket accept method
Socket sock=S.accept();
try {
System.out.println("[REMOTE HOST]: "+sock.getInetAddress().toString());
System.out.println("[LISTNING ON PORT]: "+sock.getPort());
//calling the ServeurWeb constructor
new ServeurWeb(sock,SERVER_ROOT,SERVER_HOMEPAGE,
SERVER_ICONS,SERVER_LOG);
}catch ( IOException e ) {
sock.close();//always close the socket
}
}
}finally {
S.close();//always close the ServerSocket
}
}catch(BindException B){
//handling exception generated if they are already running server
System.out.println("SERVER Already Running");
System.exit(0);
}
//
ServeurWeb()
是 ServeurWeb
类的一个实例,该类实现了 Runnable
接口。ServeurWeb
类型的对象都有一个 run()
方法,处理套接字的代码位于该方法的正文中。代码位于*AssoWebSrv.java*文件中。
//
//constructor ServeurWeb
class ServeurWeb implements Runnable {
public ServeurWeb(Socket s,String Sroot,String Shome,
String Sicons,String Slog)throws UnknownHostException,IOException{
...
run();//invoking method run() to execute the thread code
...
}
}
//
如前所述,一旦服务器收到请求,如果浏览器请求包含一个请求的文件,它就可以发送文件内容;如果 URL 只指定了一个目录,服务器就会返回*目录索引*。
//
public void Listdir(String directory,OutputStream pr,
String HOST_NAME_LINK,String Icons_Path)throws IOException {
File DIR_FILE=new File(directory);
String File_Separ_String=System.getProperty("file.separator");
String ActualDir=this.DirectoryToList(this.getDirectory_Name());
if(DIR_FILE.isDirectory()){
pr.write(new String
(""+HOST_NAME_LINK+"- /"+this.getDirectory_Name()+"").getBytes());
String[] File_List=new String[DIR_FILE.list().length];
/** File_List contain the List of files and sub directories
contained in the current folder **/
File_List=DIR_FILE.list();
...
}
}
//
其余代码可以在 WebServer
包中的*FileRead.java*文件中找到。
此时,我们的服务器不是动态的。它只能处理一些文本、HTML、图像文件。当 Web 服务器能够为用户提供交互式内容时,就称其为动态的。为此,AssoudiWebServer1.5 除了 cgi 程序外,还支持强大的脚本语言 PHP。
//
public void ProcessCgi(String CGI_PHPFile,OutputStream ToBrowser,int CGI_PHP )
throws IOException{
/**
* Method ProcessCgi according to the CGI_PHP variable ,either directly execute
* the file if it is a cgi program or call the PHP program which
*executes the PHP script
* then send the result to the server
*/
Runtime r=Runtime.getRuntime(); //creating an object Runtime by calling
//the getRuntime Method
String cgiContent=""; //cgiContent contain the program STDOUT
Process p=null; //win32 process initialised to null
switch(CGI_PHP){
case CGI_PROG:
p=r.exec(CGI_PHPFile);
break;
case PHP_PROG:
//the php program must be under the path c:\php
p=r.exec("C:\\php\\php.exe "+CGI_PHPFile);
break;
}
/*we redirect the program STDOUT to a bufferedReader */
BufferedReader brcgi=
newBufferedReader(newInputStreamReader(p.getInputStream()));
while((cgiContent=brcgi.readLine())!=null){
/**Eliminate useless data generated by the program STDOUT */
if(cgiContent.startsWith("Status")||
cgiContent.startsWith("Content")||
cgiContent.startsWith("X-Powered-By"))
{
ToBrowser.write("".getBytes());
ToBrowser.flush();
}else
{
//we send the data redirected from the program STDOUT to the client
ToBrowser.write((cgiContent+"\r\n").getBytes());
ToBrowser.flush();
}
}
//
此代码来自 package WebServer.
中的*FileRead.java*文件。
您可以看到,这里的端口号与通常的端口号 80 不同。这样做的原因之一是防止两个或多个服务器监听同一个端口号。
端口号是 AssoudiWebServer1.5 配置文件中定义的其他指令之一,以便由服务器管理员进行配置。
- # 指令
ServerRoot
保存发布目录的路径 - # 指令
Port
包含服务器监听的端口号 - # 指令
Welcome
用于包含欢迎页面 - # 指令
Icons
包含服务器图标所在的虚拟目录名称 - # 指令
LogFile
指示服务器日志文件的路径
结论
即使 AssoudiWebServer1.5 似乎能够完成高效 Web 服务器所需的大部分工作,但它仍然不完整。我在下一版本中寻找添加对原生 servlets 和一些安全功能的支持。
AssoudiWebServer1.5 的 Linux 版本(即将推出...)。
如需更多信息或建议,请通过 a_othmane@hotmail.com 联系我。
作者:Assoudi Othmane