In the previous article, I wrote a simple Java web server implementation, which can only handle some static resource requests. The Servlet container implemented in this article has been slightly modified based on the previous server, adding the processing of Servlet requests.
Program execution steps
1. Create a ServerSocket object;
2. Call the accept method of the ServerSocket object and wait for the connection. If the connection is successful, a Socket object will be returned, otherwise it will be blocked and waited;
3. Get the InputStream and OutputStream byte streams from the Socket object, and these two streams correspond to the request request and response response respectively;
4. Process the request: read the InputStream byte stream information, convert it into a string form, and parse it. The parsing here is relatively simple, and it only obtains the uri (uniform resource identifier) information;
5. Process the response (in two types, static resource request response or servlet request response): If it is a static resource request, based on the parsed uri information, find the requested resource resource file from the WEB_ROOT directory, read the resource file, and write it to the OutputStream byte stream; if it is a Servlet request, first generate a URLClassLoader class loader, load the requested servlet class, create the servlet object, and execute the service method (write the response data to OutputStream);
6. Close the Socket object;
7. Go to step 2 and continue waiting for the connection request;
Code implementation:
Add dependencies:
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.3</version> </dependency>
Server code:
package ex02.pyrmont.first;import java.net.Socket;import java.net.ServerSocket;import java.net.InetAddress;import java.io.InputStream;import java.io.OutputStream;import java.io.IOException;import ex02.pyrmont.Request;import ex02.pyrmont.Response;import ex02.pyrmont.StaticResourceProcessor;public class HttpServer1 { // Close service command private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; public static void main(String[] args) { HttpServer1 server = new HttpServer1(); //Waiting for connection request server.await(); } public void await() { ServerSocket serverSocket = null; int port = 8080; try { //Server socket object serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); } // Loop to wait for the request while (true) { Socket socket = null; InputStream input = null; OutputStream output = null; try { // Wait for the connection, after the connection is successful, return a Socket object socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // Create a Request object and parse Request request = new Request(input); request.parse(); // Check whether it is a shutdown service command if (request.getUri().equals(SHUTDOWN_COMMAND)) { break; } // Create Response object Response response = new Response(output); response.setRequest(request); if (request.getUri().startsWith("/servlet/")) { //Request uri starts with /servlet/, indicating that servlet request ServletProcessor1 processor = new ServletProcessor1(); processor.process(request, response); } else { // Static ResourceResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // Close socket socket.close(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }}Constant class:
package ex02.pyrmont;import java.io.File;public class Constants { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes"; }Request:
package ex02.pyrmont;import java.io.InputStream;import java.io.IOException;import java.io.BufferedReader;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;public class Request implements ServletRequest { private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public String getUri() { return uri; } /** * * The form of requestString is as follows: * GET /index.html HTTP/1.1 * Host: localhost:8080 * Connection: keep-alive * Cache-Control: max-age=0 * ... * The purpose of this function is to obtain /index.html string*/ 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; } //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.print(request.toString()); uri = parseUri(request.toString()); } /* implementation of the ServletRequest */ public Object getAttribute(String attribute) { return null; } public Enumeration<?> getAttributeNames() { return null; } public String getRealPath(String path) { return null; } public RequestDispatcher getRequestDispatcher(String path) { return null; } public boolean isSecure() { return false; } public String getCharacterEncoding() { return null; } public int getContentLength() { return 0; } public String getContentType() { return null; } public ServletInputStream getInputStream() throws IOException { return null; } public Locale getLocale() { return null; } public Enumeration<?> getLocales() { return null; } public String getParameter(String name) { return null; } public Map<?, ?> getParameterMap() { return null; } public Enumeration<?> getParameterNames() { return null; } public String[] getParameterValues(String parameter) { return null; } public String getProtocol() { return null; } public BufferedReader getReader() throws IOException { return null; } public String getRemoteAddr() { return null; } public String getRemoteHost() { return null; } public String getScheme() { return null; } public String getServerName() { return null; } public int getServerPort() { return 0; } public void removeAttribute(String attribute) { } public void setAttribute(String key, Object value) { } public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { }}Response:
package ex02.pyrmont;import java.io.OutputStream;import java.io.IOException;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.File;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletResponse;import javax.servlet.ServletOutputStream;public class Response implements ServletResponse { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; PrintWriter writer; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } //Write the web file into the OutputStream byte stream public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try { /* request.getUri has been replaced by request.getRequestURI */ File file = new File(Constants.WEB_ROOT, request.getUri()); fis = new FileInputStream(file); /* * HTTP Response = Status-Line(( general-header | response-header | * entity-header ) CRLF) CRLF [ message-body ] Status-Line = * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch != -1) { output.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } } catch (FileNotFoundException e) { 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>"; output.write(errorMessage.getBytes()); } finally { if (fis != null) fis.close(); } } /** implementation of ServletResponse */ public void flushBuffer() throws IOException { } public int getBufferSize() { return 0; } public String getCharacterEncoding() { return null; } public Locale getLocale() { return null; } public ServletOutputStream getOutputStream() throws IOException { return null; } public PrintWriter getWriter() throws IOException { // autoflush is true, println() will flush, // but print() will not. writer = new PrintWriter(output, true); return writer; } public boolean isCommitted() { return false; } public void reset() { } public void resetBuffer() { } public void setBufferSize(int size) { } public void setContentLength(int length) { } public void setContentType(String type) { } public void setLocale(Locale locale) { }}Static resource request processing:
package ex02.pyrmont;import java.io.IOException;public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } }}Servlet request processing:
package ex02.pyrmont.first;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import java.io.IOException;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import ex02.pyrmont.Constants;import ex02.pyrmont.Request;import ex02.pyrmont.Response;public class ServletProcessor1 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); //Classloader, used to load class URLClassLoader from a specified JAR file or directory loader = null; try { URLStreamHandler streamHandler = null; //Create class loader loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler)}); } catch (IOException e) { System.out.println(e.toString()); } Class<?> myClass = null; try { //Load the corresponding servlet class myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { //Produce servlet instance servlet = (Servlet) myClass.newInstance(); //Execute the service method of the ervlet servlet.service((ServletRequest) request,(ServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } }}Servlet class:
import javax.servlet.*;import java.io.IOException;import java.io.PrintWriter;public class PrimitiveServlet implements Servlet { public void init(ServletConfig config) throws ServletException { System.out.println("init"); } public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println("from service"); PrintWriter out = response.getWriter(); out.println("Hello. Roses are red."); out.print("Violets are blue."); } public void destroy() { System.out.println("destroy"); } public String getServletInfo() { return null; } public ServletConfig getServletConfig() { return null; }} Results test:
Static resource request:
servlet request (because it's just the first string that is refreshed to the browser, you can't see the second string Violets are blue. We'll refine the container later):
improve
The Servlet container implemented earlier has a serious problem. In the servlet, the user can directly transform ServletRequest and ServletResponse into Request and Response types, and directly call its internal public methods. This is a bad design. The improvement method is to add appearance classes to Request and Response, so that users can only access the public methods defined in the appearance class.
Request appearance class
package ex02.pyrmont.second;import java.io.IOException;import java.io.BufferedReader;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;import ex02.pyrmont.Request;public class RequestFacade implements ServletRequest { private ServletRequest request = null; public RequestFacade(Request request) { this.request = request; } /* implementation of the ServletRequest */ public Object getAttribute(String attribute) { return request.getAttribute(attribute); } public Enumeration<?> getAttributeNames() { return request.getAttributeNames(); } @SuppressWarnings("deprecation") public String getRealPath(String path) { return request.getRealPath(path); } public RequestDispatcher getRequestDispatcher(String path) { return request.getRequestDispatcher(path); } public boolean isSecure() { return request.isSecure(); } public String getCharacterEncoding() { return request.getCharacterEncoding(); } public int getContentLength() { return request.getContentLength(); } public String getContentType() { return request.getContentType(); } public ServletInputStream getInputStream() throws IOException { return request.getInputStream(); } public Locale getLocale() { return request.getLocale(); } public Enumeration<?> getLocales() { return request.getLocales(); } public String getParameter(String name) { return request.getParameter(name); } public Map<?, ?> getParameterMap() { return request.getParameterMap(); } public Enumeration<?> getParameterNames() { return request.getParameterNames(); } public String[] getParameterValues(String parameter) { return request.getParameterValues(parameter); } public String getProtocol() { return request.getProtocol(); } public BufferedReader getReader() throws IOException { return request.getReader(); } public String getRemoteAddr() { return request.getRemoteAddr(); } public String getRemoteHost() { return request.getRemoteHost(); } public String getScheme() { return request.getScheme(); } public String getServerName() { return request.getServerName(); } public int getServerPort() { return request.getServerPort(); } public void removeAttribute(String attribute) { request.removeAttribute(attribute); } public void setAttribute(String key, Object value) { request.setAttribute(key, value); } public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { request.setCharacterEncoding(encoding); }} Response Appearance Class
package ex02.pyrmont.second;import java.io.IOException;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletResponse;import javax.servlet.ServletOutputStream;import ex02.pyrmont.Response;public class ResponseFacade implements ServletResponse { private ServletResponse response; public ResponseFacade(Response response) { this.response = response; } public void flushBuffer() throws IOException { response.flushBuffer(); } public int getBufferSize() { return response.getBufferSize(); } public String getCharacterEncoding() { return response.getCharacterEncoding(); } public Locale getLocale() { return response.getLocale(); } public ServletOutputStream getOutputStream() throws IOException { return response.getOutputStream(); } public PrintWriter getWriter() throws IOException { return response.getWriter(); } public boolean isCommitted() { return response.isCommitted(); } public void reset() { response.reset(); } public void resetBuffer() { response.resetBuffer(); } public void setBufferSize(int size) { response.setBufferSize(size); } public void resetBufferSize(size); } public void resetBufferSize(int size); } public void setBufferSize(size); } public void setContentLength(int length) { response.setContentLength(length); } public void setContentType(String type) { response.setContentType(type); } public void setLocale(Locale locale) { response.setLocale(locale); }}Processing Servlet request class:
package ex02.pyrmont.second;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import java.io.IOException;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import ex02.pyrmont.Constants;import ex02.pyrmont.Request;import ex02.pyrmont.Response;public class ServletProcessor2 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); // Class loader, used to load class URLClassLoader from a specified JAR file or directory loader = null; try { URLStreamHandler streamHandler = null; // Create class loader loader = new URLClassLoader(new URL[] { new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler) }); } catch (IOException e) { System.out.println(e.toString()); } Class<?> myClass = null; try { // Load the corresponding servlet class myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; //Add appearance classes to request and response, and security considerations are given to prevent users from directly transforming ServletRequest and ServletResponse into Request and Response types in servlets, // and directly call their internal public methods, because there will be no parse, sendStaticResource and other methods in RequestFacade and ResponseFacade; RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } }} The other codes are basically the same as the Servlet container implemented earlier.
The verification program requests static resources and servlets respectively, and finds that the results are consistent with the container implemented earlier;
Reference: "In-depth Analysis of Tomcat"
@author A wind-like coder
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.