对于第一篇文章中的web服务器而言,它是非常简单的,它仅仅能响应一个静态的HTTP请求。在本文中,我们将看到如何一步步的搭建一个可以响应动态Servlet请求的web服务器。
2.1回顾javax.servlet.Servlet接口
2.1.1
我们首先回顾下Servlet接口,它是Servlet编程中最为重要的一个接口,它的定义如下:
当Servlet容器调用某个Servlet类的时候,init()方法被调用,且只执行一次;在Servlet处理任何请求之前,必须保证其被正确初始化;当Servlet的一个客户端请求到达以后,容器调用Servlet的service()方法,并将ServletRequest和ServletResponse两个对象作为参数传给该方法,在Servlet生命周期内,该方法可以被多次调用;在将Servlet实例从服务中移除的时候,destroy()方法被调用。
下面是本章的一个Servlet程序。如下:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
importjavax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
publicclass PrimitiveServletimplements Servlet {
@Override
publicvoid destroy() {
System.out.println("destory");
}
@Override
public ServletConfig getServletConfig() {
returnnull;
}
@Override
public String getServletInfo() {
returnnull;
}
@Override
publicvoid init(ServletConfig arg0)throwsServletException {
System.out.println("init");
}
@Override
publicvoid service(ServletRequest request, ServletResponse response)
throwsServletException,IOException {
System.out.println("fromservice");
PrintWriter out=response.getWriter();
out.println("Hello.Roses are red.");
out.println("violets areblue.");
}
}
PrimitiveServlet 非常简单,需要注意的是:我把它和它的class文件放在了webroot目录下。
2.2 应用程序1
2.1.1HttpServer1
HttpServer1与第一章相比,大的区别就是在awit()方法中加入了对请求动态内容(Servlet请求)的处理。它的示例如下:
package ex02;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
publicclass HttpServer1 {
privatestaticbooleanshutdown=false;
publicstaticvoid main(String[] args){
new HttpServer1().await();
}
privatevoid await(){
int port=7890;
ServerSocket serverSocket=null;
try{
serverSocket=new ServerSocket(port,1,
InetAddress.getByName("127.0.0.1"));
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
try {
Socket socket=null;
InputStream input=null;
OutputStream output=null;
while(!shutdown){
socket=serverSocket.accept();
input=socket.getInputStream();
output=socket.getOutputStream();
HttpRequest request=new HttpRequest(input);
request.parse();
HttpResponse response=new HttpResponse(output);
response.setRequest(request);
if(request.getUri().startsWith("/servlet")){
ServletProcessor1 processor1=newServletProcessor1();
processor1.process(request, response);
}else{
StaticResourceProcessor processor=new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();
shutdown=request.getUri().equals("shutdown");
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.1.2Request
本章的HttpRequest与上一章的区别在于:它实现了ServletRequest接口。如下:
package ex02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
publicclass HttpRequestimplements ServletRequest {
private InputStreaminput;
private Stringuri;
public String getUri() {
returnuri;
}
publicvoid setUri(String uri) {
this.uri = uri;
}
public InputStream getInput() {
returninput;
}
publicvoid setInput(InputStream input) {
this.input = input;
}
public HttpRequest(){
}
public HttpRequest(InputStream input){
this.input=input;
}
//解析HTTP请求的原始数据
publicvoid parse(){
byte[] b=newbyte[2048];
StringBuffer request=new StringBuffer(2048);
try {
int i=input.read(b);
for(int j=0;j<i;j++){
request.append((char)b[j]);
}
} catch (IOException e) {
e.printStackTrace();
}
this.uri=parseUri(request.toString());
System.out.println("HTTP请求的原始数据是:"+request.toString());
System.out.println("uri:"+uri);
}
//解析HTTP请求的URL
private String parseUri(String requestString){
int index1,index2;
index1=requestString.indexOf(' ');
if(index1!=-1){
index2=requestString.indexOf(' ', index1+1);
if(index2>index1){
return requestString.substring(index1+1, index2);
}
}
returnnull;
}
@Override
public Object getAttribute(String arg0) {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicEnumeration getAttributeNames() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getCharacterEncoding() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicint getContentLength() {
//TODO Auto-generatedmethod stub
return 0;
}
@Override
public String getContentType() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public ServletInputStream getInputStream()throws IOException {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getLocalAddr() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getLocalName() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicint getLocalPort() {
//TODO Auto-generatedmethod stub
return 0;
}
@Override
public Locale getLocale() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicEnumeration getLocales() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getParameter(String arg0) {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicMap getParameterMap() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicEnumeration getParameterNames() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String[] getParameterValues(String arg0) {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getProtocol() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public BufferedReader getReader()throws IOException {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getRealPath(String arg0) {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getRemoteAddr() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getRemoteHost() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicint getRemotePort() {
//TODO Auto-generatedmethod stub
return 0;
}
@Override
public RequestDispatcher getRequestDispatcher(String arg0) {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getScheme() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getServerName() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
publicint getServerPort() {
//TODO Auto-generatedmethod stub
return 0;
}
@Override
publicboolean isSecure() {
//TODO Auto-generatedmethod stub
returnfalse;
}
@Override
publicvoid removeAttribute(String arg0) {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setAttribute(String arg0, Object arg1) {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setCharacterEncoding(String arg0)
throws UnsupportedEncodingException {
//TODO Auto-generatedmethod stub
}
}
2.1.3Response
与HttpRequest类似,除了getWriter()方法外,其他方法都是空实现。
package ex02;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
publicclass HttpResponseimplements ServletResponse {
privatestaticfinalintBUFFER_SIZE=1024;
HttpRequest request;
OutputStream output;
PrintWriter writer;
public HttpResponse(){
}
public HttpResponse(OutputStream output){
this.output=output;
}
public HttpRequest getRequest() {
returnrequest;
}
publicvoid setRequest(HttpRequest request) {
this.request = request;
}
public OutputStream getOutput() {
returnoutput;
}
publicvoid setOutput(OutputStream output) {
this.output = output;
}
publicvoid sendStaticResource(){
byte[] bytes=newbyte[BUFFER_SIZE];
FileInputStream fis=null;
File file=new File(Constant.WEB_ROOT,request.getUri());
try {
if(file.exists()){
fis=new FileInputStream(file);
int ch=fis.read(bytes,0,BUFFER_SIZE);
while(ch!=-1){
output.write(bytes, 0, ch);
ch=fis.read(bytes,0,BUFFER_SIZE);
}
}
else{
output.write("file notfound".getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(fis!=null){
try{
fis.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
@Override
publicvoid flushBuffer()throws IOException {
//TODO Auto-generatedmethod stub
}
@Override
publicint getBufferSize() {
//TODO Auto-generatedmethod stub
return 0;
}
@Override
public String getCharacterEncoding() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public String getContentType() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public Locale getLocale() {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public ServletOutputStream getOutputStream()throws IOException {
//TODO Auto-generatedmethod stub
returnnull;
}
@Override
public PrintWriter getWriter()throws IOException {
writer=new PrintWriter(output,true);
returnwriter;
}
@Override
publicboolean isCommitted() {
//TODO Auto-generatedmethod stub
returnfalse;
}
@Override
publicvoid reset() {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid resetBuffer() {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setBufferSize(int arg0) {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setCharacterEncoding(String arg0) {
// TODO Auto-generated method stub
}
@Override
publicvoid setContentLength(int arg0) {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setContentType(String arg0) {
//TODO Auto-generatedmethod stub
}
@Override
publicvoid setLocale(Locale arg0) {
//TODO Auto-generatedmethod stub
}
}
2.1.4 Processor
原书中没有这个类,这个类是一个抽象类(用它的好处是:可以面对接口编程,代码可以更容易扩展)
package ex02;
publicabstractclass Processor {
publicabstractvoid process(HttpRequest request,HttpResponse response);
}
2.1.5StaticResourceProcessor
StaticResourceProcessor,顾名思义,就是处理静态资源的。
package ex02;
publicclass StaticResourceProcessorextends Processor {
publicvoid process(HttpRequest request,HttpResponse response){
response.sendStaticResource();
}
}
2.1.6ServletProcessor1
这个类呢,就是处理Servlet请求的。需要注意的是:我们可以请求其他类似PrimitiveServlet的Servlet类,前提是需要将其class文件放在webroot目录下,在后续的章节中,会解决之歌问题。
假设客户端的请求是:http://localhost:7890/servlet/PrimitiveServlet,那么这个类的process()方法做的事情是:
1. 根据请求的url获取servletName,此例servletName的值为PrimitiveServlet
2. 接下来,该方法会载入以serverName为名称的类,为了载入该类,需要创建一个
一个类载入器,类载入器的构造函数如下图:
对于本章的应用程序而言,类载入器会到Contants.WEB_ROOT指定的目录下查找需要载入的类。
3. 在servlet中,类载入器查找servlet类的目录称为仓库(repository),在2中,URLClassLoader需要一个URL数组,可以使用下面的语句new:
URLStreamHandlerstreamHandler=null;
String repository = (new URL("file",null,lassPath.getCanonicalPath()
+ File.separator)).toString();
URL[] urls=new URL[1];
urls[0]=new URL(null,repository,streamHandler);
4.于是我们创建了URLClassLoader的实例,然后可以调用它的loadClass(String name)
方法加载我们的Servlet类:
Class myClass=null;
Class=loader.loadClass(servletName);
Servlet servlet=null;
servlet=(Servlet) myClass.newInstance();
servlet.service((ServletRequest)request, (ServletResponse)response);
package ex02;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
publicclass ServletProcessor1extends Processor {
publicvoid process(HttpRequest request,HttpResponse response){
String uri=request.getUri();
String servletName=uri.substring(uri.lastIndexOf("/")+1);
URLClassLoader loader=null;
try{
URL[] urls = new URL[1];
URLStreamHandlerstreamHandler =null;
File classPath = new File(Constant.WEB_ROOT);
String repository= (new URL("file",null, classPath.getCanonicalPath() + File.separator)).toString();
urls[0] = new URL(null, repository,streamHandler);
loader = newURLClassLoader(urls);
}catch(Exception e){
e.printStackTrace();
}
Class myClass=null;
try{
myClass=loader.loadClass(servletName);
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Servlet servlet=null;
try {
servlet=(Servlet) myClass.newInstance();
try {
servlet.service((ServletRequest)request,(ServletResponse)response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
2.1.7Conants
package ex02;
import java.io.File;
publicclass Constants{
publicstaticfinal StringWEB_ROOT = System.getProperty("user.dir")
+File.separator+"webroot";
}
2.1.8运行
要想该程序达到预期目标,除了coding之外,还需将资源放在webroot目录下,例如:
我们可以使用下图命令对TestServlet.java编译:
2.3应用程序2
应用程序1存在一个严重的问题,请看如下代码:
Public voidprocess(HttpRequest request,HttpResponse response){
//…
servlet=(Servlet) myClass.newInstance();
servlet.service((ServletRequest)request,(ServletResponse)response);
//….
}
上段代码将HttpRequest和HttpResponse类的实例分别向上转型为ServletRequest和ServletResponse类的实例,并将它们作为参数传递给service方法。熟悉应用程序1的程序员可以这么做:
javax.servlet.ServletRequestmyServlet=new ex02.HttpRequest();//向上转型
ex02.HttpRequest request=(ex02.HttpRequest) myServlet;//向下转型
request.parse();
request.getUri();
有了HttpRequest对象后,这里可以调用它的parse和geUri方法,同理可以调用HttpResponse的sendStaticResource方法。我们可以使用外观类来解决这个问题。
HttpRequestFacade.java关键代码如下:
publicclass HttpRequestFacade implements ServletRequest {
private ServletRequest request;
public HttpRequestFacade(ServletRequest request) {
this.request = request;
}
//override ……
}
HttpResponseFacade.java与上述代码极为相似。
在ServletProcessor2.java与应用程序1中的ServletProcessor1.java的区别在于:
servlet=(Servlet)myClass.newInstance();
HttpRequestFacaderequestFacade=new HttpRequestFacade(request);
HttpResponseFacaderesponseFacade=new HttpResponseFacade(response);
servlet.service((ServletRequest)requestFacade,(ServletResponse)responseFacade);
除此之外,并无不同之处。