最近学习tomcat的结构,书是比较早的《how tomcat works》,上一章的示例是一个简单的web服务器,这一章是在之前的基础上扩展,增加了servlet的内容。整个编码完成后用java / javac 命令运行,可以访问servlet容器。
本示例总共有6个类
- HttpServer(一个ServerSocket服务器用来接收请求)
- Request (请求类,用来解释socket请求信息,实现了ServletRequest)
- Response (响应类,根据请求寻找资源并返回)
- StaticResourceProcessor (处理静态资源的请求)
- ServletProcessor (处理servlet请求)
- Constants (常量类)
- PrimitiveServlet (简单的servlet实现类)
1.HttpServer.java
package com.chl.webserver.servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpServer {
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer hs = new HttpServer();
hs.await();
}
//循环等待请求
public void await() {
ServerSocket ss = null;
int port = 8080;
try {
ss = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
}catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
while(!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = ss.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
Request req = new Request(input);
req.parse();
Response res = new Response(output);
res.setRequest(req);
if(req.getUri().startsWith("/servlet/")) {
ServletProcessor sp = new ServletProcessor();
sp.process(req, res);
}else {
StaticResourceProcessor sr = new StaticResourceProcessor();
sr.process(req, res);
}
socket.close();
shutdown = req.getUri().equals(SHUTDOWN_COMMAND);
}catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
2.Request.java
package com.chl.webserver.servlet;
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.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class Request implements ServletRequest {
// 输入流
private InputStream input;
// 地址
private String uri;
public Request(InputStream input) {
this.input = input;
}
public String getUri() {
return uri;
}
//解析http请求中的原始数据。
void parse() {
//Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i ;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}catch(IOException e) {
e.printStackTrace();
i = -1;
}
for(int j = 0; j< i;j++) {
request.append((char)buffer[j]);
}
System.out.println(request.toString());
uri = parseUri(request.toString());
}
//从 url中返回uri
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);
}
}
return null;
}
@Override
public Object getAttribute(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
}
@Override
public int getContentLength() {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getContentLengthLong() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getParameter(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getParameterNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String[] getParameterValues(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, String[]> getParameterMap() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getProtocol() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getScheme() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getServerName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getServerPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public BufferedReader getReader() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteHost() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setAttribute(String name, Object o) {
// TODO Auto-generated method stub
}
@Override
public void removeAttribute(String name) {
// TODO Auto-generated method stub
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<Locale> getLocales() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRealPath(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public int getRemotePort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getLocalName() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getLocalAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getLocalPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public ServletContext getServletContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isAsyncStarted() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAsyncSupported() {
// TODO Auto-generated method stub
return false;
}
@Override
public AsyncContext getAsyncContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public DispatcherType getDispatcherType() {
// TODO Auto-generated method stub
return null;
}
}
3.Response.java
package com.chl.webserver.servlet;
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;
public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
PrintWriter writer;
//构造函数创建output对象
public Response(OutputStream output) {
this.output = output;
}
//传递request对象给reponse
public void setRequest(Request request) {
this.request = request;
}
// 输出一个静态资源
public void sendStaticResource() throws IOException{
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(Constants.WEB_ROOT,request.getUri());
if(file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes,0,BUFFER_SIZE);
while( ch != -1) {
output.write(bytes,0,BUFFER_SIZE);
ch = fis.read(bytes,0,BUFFER_SIZE);
}
}else { //如果不存在,则发送一个异常信息
String errorMessage = "HTTP/1.1 404 File Not Found \r\n"+
"Content-Type : text/html\r\n"+
"Content-Length:23\r\n"+
"\r\n"+
"<h1>File Not Found</h1>";
System.out.println(errorMessage);
output.write(errorMessage.getBytes());
}
}catch(Exception e) {
System.out.println(e.toString());
}finally {
if(fis != null)
fis.close();
}
}
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public PrintWriter getWriter() throws IOException {
writer = new PrintWriter(output,true);
return writer;
}
@Override
public void setCharacterEncoding(String charset) {
// TODO Auto-generated method stub
}
@Override
public void setContentLength(int len) {
// TODO Auto-generated method stub
}
@Override
public void setContentLengthLong(long len) {
// TODO Auto-generated method stub
}
@Override
public void setContentType(String type) {
// TODO Auto-generated method stub
}
@Override
public void setBufferSize(int size) {
// TODO Auto-generated method stub
}
@Override
public int getBufferSize() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void flushBuffer() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void resetBuffer() {
// TODO Auto-generated method stub
}
@Override
public boolean isCommitted() {
// TODO Auto-generated method stub
return false;
}
@Override
public void reset() {
// TODO Auto-generated method stub
}
@Override
public void setLocale(Locale loc) {
// TODO Auto-generated method stub
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
}
4.StaticResourceProcessor.java
package com.chl.webserver.servlet;
import java.io.IOException;
/**
* 处理对静态资源的请求
* @author chenhailong
*
*/
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResource();
}catch(IOException e) {
e.printStackTrace();
}
}
}
5.ServletProcessor.java
package com.chl.webserver.servlet;
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.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletProcessor {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
System.out.println("classPath.getCanonicalPath()--" + classPath.getCanonicalPath());
String repository = (new URL("file",null,classPath.getCanonicalPath() + File.separator)).toString();
urls[0] = new URL(null, repository,streamHandler);
loader = new URLClassLoader(urls);
}catch(IOException e) {
System.out.println(e.toString());
}
Class myClass = null;
try {
String curPackageName = ServletProcessor.class.getPackage().getName();
servletName = curPackageName + "." + servletName;
myClass = loader.loadClass(servletName);
}catch(ClassNotFoundException e) {
}
Servlet servlet = null;
try {
servlet = (Servlet)myClass.newInstance();
servlet.service((ServletRequest)request, (ServletResponse)response);
}catch(Exception e) {
System.out.println("+++"+e.toString());
}catch(Throwable e) {
System.out.println("###"+e.toString());
}
}
}
6.Constants.java
package com.chl.webserver.servlet;
import java.io.File;
public class Constants {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "com/chl/webserver/servlet/";
}
Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。所有的servlet必须实现或继承实现该接口的类。
7.PrimitiveServlet.java
package com.chl.webserver.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
/**
* Servlet implementation class PrimitiveServlet
* 所有的servlet程序都必须实现该接口或继承自实现了该接口的类。
*/
public class PrimitiveServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
//生命周期方法,可以通过覆盖该方法来初始化对象,如加载数据库,值初始化
}
@Override
public ServletConfig getServletConfig() {
// TODO Auto-generated method stub
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//req包含HTTP请求信息,res可以封装servlet的响应。
System.out.println("are you ok !");
PrintWriter out = res.getWriter();
out.println("Hello , Roses are red.");
}
@Override
public String getServletInfo() {
// TODO Auto-generated method stub
return null;
}
@Override
public void destroy() {
//通常在servlet容器正在关闭正在被关闭或者servlet容器需要一些空闲内存的时候调用,尽在所有servlet线程的service方法已经退出或者超时淘汰的时候。
System.out.println("des !");
}
}
先执行编译类文件
javac -classpath /Users/chenhailong/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar -g ./com/chl/webserver/servlet/*.java
./com/chl/webserver/servlet/Request.java:11: 错误: 找不到符号
import javax.servlet.AsyncContext;
^
符号: 类 AsyncContext
位置: 程序包 javax.servlet
./com/chl/webserver/servlet/Request.java:12: 错误: 找不到符号
import javax.servlet.DispatcherType;
^
符号: 类 DispatcherType
位置: 程序包 javax.servlet
./com/chl/webserver/servlet/Request.java:252: 错误: 找不到符号
public AsyncContext startAsync() throws IllegalStateException {
^
...
...
...
./com/chl/webserver/servlet/Response.java:98: 错误: 方法不会覆盖或实现超类型的方法
@Override
^
注: ./com/chl/webserver/servlet/Request.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
15 个错误
处理:原因是我引入了 servlet-api-2.5.jar 和 javax.servlet-api-4.0.1.jar应该使用第二个包
修改jar包名称后可以看到编译成功
chenhailongdeMacBook-Pro:src chenhailong$ javac -classpath /Users/chenhailong/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar -g ./com/chl/webserver/servlet/*.java
注: ./com/chl/webserver/servlet/Request.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
然后运行项目
chenhailongdeMacBook-Pro:servlet chenhailong$ java -classpath /Users/chenhailong/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar HttpServer
错误: 找不到或无法加载主类 HttpServer
是因为类中有写包名,运行的时候需要带上包名(从包最外层执行)
## 执行
chenhailongdeMacBook-Pro:src chenhailong$ java com.chl.webserver.servlet.HttpServer
## 查看运行状态
chenhailongdeMacBook-Pro:Java chenhailong$ jps -v
63738 HttpServer
63739 Jps -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home -Xms8m
可以看到 HttpServer已经正常运行了
然后在地址栏访问 http://127.0.0.1:8080/servlet/PrimitiveServlet会报错如下
chenhailongdeMacBook-Pro:src chenhailong$ java com.chl.webserver.servlet.HttpServer
Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/ServletRequest
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.chl.webserver.servlet.HttpServer.await(HttpServer.java:43)
at com.chl.webserver.servlet.HttpServer.main(HttpServer.java:19)
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletRequest
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 14 more
这是由于启动时没有指定 classpath的servlet-api.jar包,重新运行如下:
java -classpath /Users/chenhailong/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar:./ com.chl.webserver.servlet.HttpServer
重新访问http://127.0.0.1:8080/servlet/PrimitiveServlet
java.lang.ClassNotFoundException: Primitiveservlet
java.lang.NullPointerException
GET /servlet/PrimitiveServlet HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
或者报错
Exception in thread "main" java.lang.NoClassDefFoundError: PrimitiveServlet (wrong name: com/chl/webserver/servlet/PrimitiveServlet)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.chl.webserver.servlet.ServletProcessor.process(ServletProcessor.java:39)
at com.chl.webserver.servlet.HttpServer.await(HttpServer.java:51)
at com.chl.webserver.servlet.HttpServer.main(HttpServer.java:19)
是因为 myClass = loader.loadClass(servletName);
这里的类名需要加 包名。
修改之后正常运行并输出内容
are you ok !