概述
WEB服务器动态执行的程序可分为两种方式。
- 第一种方式:完全用编程语言编写的程序,例如CGI(Commond Gateway Interface)程序和Java编写的Servlet程序。
- 第二种方式:嵌入了程序代码的HTML文档,例如PHP、ASP和JSP文档。JSP文档是指嵌入了Java代码的HTML文档。
在此,以Servlet为例,介绍Web服务器动态执行完全用编程语言编写的程序的原理。
值得注意的是:Web服务器动态执行特定程序代码,其特征是Web服务器在运行时加载并执行由第三方提供测程序代码。所谓Web服务器动态生成HTML文档,就是指Web服务器在运行时才通过执行特定程序代码来生成HTML文档,而不是直接从文件系统中获取已经存在的HTML文档。
执行过程如下图所示,在服务器端存放一个使用Java语言编写的程序.class文件:HelloServlet.class。当用户在浏览器端输入指向该类的URL时,Web服务器就会运行HelloServlet类,HelloServlet类生成HTML文档,并把它发送给浏览器。
HTTPServer1-服务器程序主类
HTTPServer1自生的业务逻辑规定:如果用户请求URI位于servlet子目录下,就按照Servlet来处理,否则,就按照普通的静态文件来处理。当客户请求访问特定的Servlet时,HTTPServer1先从自己的servletCache缓存中寻找特定的Servlet实例,把它放入servletCache缓存中,再调用它的service()方法。
package server;
import java.io.*;
import java.net.*;
import java.util.*;
public class HttpServer1 {
private static Map<String, Servlet> servletCache = new HashMap<String, Servlet>();
public static void main(String args[])
{
int port;
ServerSocket serverSocket;
try {
port = Integer.parseInt(args[0]);
}catch(Exception e) {
System.out.println("port = 8080(默认)");
port = 8080;
}
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器监听端口:"+serverSocket.getLocalPort());
//服务器在一个无限循环中不断接收来自客户端的TCP连接请求
while(true)
{
try {
//等待客户端的TCP请求连接
final Socket socket = serverSocket.accept();
System.out.println("建立了与客户的一个新的TCP连接,该客户的地址为:"+socket.getInetAddress()+":"+socket.getPort());
//响应客户请求
service(socket);
}catch(Exception e){
System.out.println("客户端请求的资源不存在");
}
}
}catch(Exception e) { e.printStackTrace();}
}
//响应客户端的HTTP请求
public static void service(Socket socket)throws Exception{
//读取HTTP请求信息
//获取输入流
InputStream socketIn = socket.getInputStream();
Thread.sleep(500);//睡眠500毫秒等待HTTP请求
int size = socketIn.available();
byte[] requestBuffer = new byte[size];
socketIn.read(requestBuffer);
String request = new String(requestBuffer);
System.out.println(request); //打印HTTP请求
//解析HTTP请求
//获取HTTP请求的第一行
int endIndex = request.indexOf("\r\n");
if(-1 == endIndex)
endIndex = request.length();
String firstLineOfRequest = request.substring(0, endIndex);
//解析HTTP请求的第一行
String[] parts = firstLineOfRequest.split(" ");
String uri = "";
if(2 <= parts.length)
{
//获取HTTP请求中的uri
uri = parts[1];
}
//如果请求访问Servelet,则动态调用Servlet对像的service()方法
if(-1 != uri.indexOf("servlet"))
{
//获取Servlet的名字
String servletName = null;
if(-1 != uri.indexOf("?"))
servletName = uri.substring(uri.indexOf("servlet/")+8, uri.indexOf("?"));
else
servletName = uri.substring(uri.indexOf("servlet/")+8, uri.length());
//尝试从servlet缓存中获取Servlet对像
Servlet servlet = servletCache.get(servletName);
//如果Servlet缓存中不存在Servlet对像,则创建它,并把他放在Servlet缓存中
if(null == servlet) {
servlet = (Servlet)Class.forName("server."+servletName).getDeclaredConstructor().newInstance();
servlet.init(); //先调用Servlet对像init方法
servletCache.put(servletName, servlet);
}
//调用Servlet的service方法
servlet.service(requestBuffer, socket.getOutputStream());
//睡眠1秒,等待客户端接受HTTP响应结果
Thread.sleep(1000);
socket.close();
return;
}
//决定HTTP响应正文的类型
String contentType;
if(-1 != uri.indexOf("html") || -1 != uri.indexOf("htm"))
contentType = "text/html";
else if(-1 != uri.indexOf("jpg") || -1 != uri.indexOf("jpeg"))
contentType = "image/jpeg";
else
contentType = "application/octet-stream";
//创建HTTP响应结果
//HTTP响应的第一行
String responseFirstLine = "HTTP/1.1 200 ok\r\n";
//HTTP响应头
String responseHeader = "Content-Type:"+contentType+"\r\n\r\n";
//获得读取响应正文数据的输入流
InputStream in = HttpServer1.class.getResourceAsStream("root/"+uri);
//发送HTTP响应结果
OutputStream socketOut = socket.getOutputStream();//获得输出流
//发送HTTP响应的第一行
socketOut.write(responseFirstLine.toString().getBytes());
//发送HTTP的响应头
socketOut.write(responseHeader.toString().getBytes());
//发送HTTP响应额正文
int len = 0;
byte[] buffer = new byte[128];
while(-1 != (len = in.read(buffer)))
{
socketOut.write(buffer, 0, len);
}
//睡眠1秒,等待客户端接收HTTP响应结果
Thread.sleep(1000);
//关闭TCP连接
socket.close();
}
}
Servelet-服务程序接口
Servlet接口有一个init()方法和一个service()方法:
- init()方法:为初始化方法,当HTTPServlet1创建了实现该接口的类的一个实例后,就会立即调用该实例的init()方法。
- service()方法:用于响应HTTP请求,产生具体的HTTP响应结果。HTTPServer1服务器在响应HTTP请求时会调用实现了Servlet接口的特定类的service()方法。
package server;
import java.io.*;
public interface Servlet {
public void init()throws Exception;
public void service(byte[] requestBuffer, OutputStream out)throws Exception;
}
HelloServlet-Servlet接口具体实现
HelloServlet类实现了Servlet接口。HelloServlet类的service()方法能解析HTTP请求中的请求参数,根据请求参数username的取值来生成HTML文档。
package server;
import java.io.OutputStream;
public class HelloServlet implements Servlet {
@Override
public void init() throws Exception {
// TODO 自动生成的方法存根
System.out.println("HelloServlet is inited");
}
@Override
public void service(byte[] requestBuffer, OutputStream out) throws Exception {
// TODO 自动生成的方法存根
String request = new String(requestBuffer);
//获得HTTP请求的第一行
String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
//解析HTTP请求第一行
String[] parts = firstLineOfRequest.split(" ");
String method = parts[0]; //获取HTTP请求中的请求方式
String uri = parts[1]; //获取HTTP请求中的uri
//获得请求参数username
String username = null;
//如果请求方式为GET,则请求参数紧跟HTTP请求的第一行的uri的后面
if(method.equalsIgnoreCase("get") && -1 != uri.indexOf("username"))
{
/* 假定uri="servlet/HelloServlet?username=Tom&password=1234" */
//parameters"username=Tom&password=1234"
String parameters = uri.substring(uri.indexOf("?"), uri.length());
//parts={"username=Tom","password=1234"}
parts = parameters.split("&");
//parts={"username","Tom"}
parts = parts[0].split("=");
username = parts[1];
}
//如果请求方式为“POST”,则请求参数位于HTTP请求的请求正文中。
else if(method.equalsIgnoreCase("post")){
int locate=request.indexOf("\r\n\r\n");
//获得响应正文
String content=request.substring(locate+4,request.length());
if(content.indexOf("username=")!=-1){
/*假定content="username=Tom&password=1234"*/
//parts={"username=Tom","password=1234"};
parts=content.split("&");
//parts={"username","Tom"};
parts=parts[0].split("=");
username=parts[1];
}
}
else
{
System.out.println("查找方法失败!!");
}
//创建并发送HTTP响应
//发送HTTP响应第一行
out.write("HTTP/1.1 200 ok\r\n".getBytes());
//发送HTTP响应头
out.write("Content-Type:text/html\r\n\r\n".getBytes());
//发送HTTP响应正文
String content = "<html><head><title>hello world</title></head><body>";
content += "<h1>Hello:" + username + "</h1><body><head>";
out.write(content.getBytes());
}
}
运行结果
先运行服务器程序,再通过浏览器输入URL:
http://localhost:8080/servlet/HelloServlet?username=georgeHotz
以上URL中?后面的就是请求参数,参数名为username,参数值为georgeHotz。对于此URL浏览器接受的HTML页面如下图:
当username参数值改变时,通过执行结果可知,返回结果也会跟着改变。尽管浏览器访问的都是HelloServlet,但是服务器端会根据请求参数的不同值来生成不同的HTML文档,这体现了服务器端动态生成网页的功能,而且也体现了服务器端与用户的交互功能。