前回まで
Javaによる簡易HTTPサーバの作り方
Java100行弱で10分以内に読める!Tomcat無しでサーブレットを実行する最小コンテナを自作してみた
Java100行弱で10分以内に読める!Tomcat無しでサーブレットを実行する最小コンテナを自作してみた第2回web.xml導入
今回の肝
コンテナ起動時に自動的にJSPをサーブレット(classファイル)へコンパイルできるようにしました。
JspCompiler.java
の中でjspの中身をwrite.out()
へ書き込む簡単なjavaのソースへ変換します。
その後MiniContainer.js
のaddJsp
メソッドの中でclassファイルへ変換し、インスタンスを生成しています。
動作
コンテナの実装
MiniContainer.js
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.tools.*;
import java.net.URLClassLoader;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
public class MiniContainer {
private static final Map<String, MyServlet> routes = new HashMap<>();
public static void main(String[] args) throws Exception {
// JSPを登録
addJsp("/hello.jsp", "hello.jsp");
// ユーザが作ったServletクラスをロードして登録
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("web.xml"));
NodeList list = doc.getElementsByTagName("servlet");
for (int i = 0; i < list.getLength(); i++) {
Element el = (Element) list.item(i);
String path = el.getAttribute("path");
String clazz = el.getAttribute("class");
addServletFromClass(path, clazz);
}
//web.xml導入前
//addServletFromClass("/hello", "HelloServlet");
//addServletFromClass("/bye", "ByeServlet");
ServerSocket server = new ServerSocket(8081);
System.out.println("Running on http://localhost:8081/");
while (true) {
Socket client = server.accept();
handleClient(client);
}
}
// クラス名からユーザのサーブレットをロードして登録
private static void addServletFromClass(String path, String className) throws Exception {
Class<?> clazz = Class.forName(className); // クラスをロード
MyServlet servlet = (MyServlet) clazz.getDeclaredConstructor().newInstance();
routes.put(path, servlet);
}
private static void handleClient(Socket client) throws Exception {
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
// ブラウザから送られてくるHTTPリクエスト1行目を読む
char[] buf = new char[1024];
int idx = 0;
int c;
while ((c = in.read()) != -1) {
if (c == '\r') continue; // HTTPではCRLFなのでCRは無視
if (c == '\n') break; // LFで1行終わり
buf[idx++] = (char)c;
}
String requestLine = new String(buf, 0, idx);
System.out.println("Request: " + requestLine);
// 例 "GET /hello HTTP/1.1" → ["GET", "/hello", "HTTP/1.1"]
// "/hello"だけ取り出す
String path = "/";
if (!requestLine.isEmpty()) {
path = requestLine.split(" ")[1];
}
MyServlet servlet = routes.get(path);
if (servlet != null) {
servlet.service(in, out); // ユーザが作ったサーブレットを呼び出す
} else {
String body = "<h1>404 Not Found</h1>";
String headers = "HTTP/1.1 404 Not Found\r\n" +
"Content-Length: " + body.getBytes("UTF-8").length + "\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n\r\n";
out.write(headers.getBytes("UTF-8"));
out.write(body.getBytes("UTF-8"));
}
out.flush();
client.close();
}
private static void addJsp(String path, String jspFile) throws Exception {
String className = jspFile.replace(".jsp", "_jsp");
// JSP→Java生成
String javaFile = JspCompiler.compile(jspFile, className);
// 実行時コンパイル
JavaCompiler compiler = javax.tools.ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new IllegalStateException("JDKで実行してください (JREでは不可)");
}
int result = compiler.run(null, null, null, javaFile);
if (result != 0) throw new RuntimeException("コンパイル失敗: " + javaFile);
// .classをロード
URLClassLoader loader = URLClassLoader.newInstance(new URL[]{new File(".").toURI().toURL()});
Class<?> clazz = Class.forName(className, true, loader);
// インスタンス化して登録
MyServlet servlet = (MyServlet) clazz.getDeclaredConstructor().newInstance();
routes.put(path, servlet);
}
}
MyServlet.java
import java.io.*;
public interface MyServlet {
void service(InputStream in, OutputStream out) throws Exception;
}
JspCompiler.java
import java.nio.file.*;
import java.io.*;
public class JspCompiler {
public static String compile(String jspFile, String className) throws Exception {
String jsp = Files.readString(Paths.get(jspFile));
StringBuilder javaSrc = new StringBuilder();
javaSrc.append("import java.io.*;\n");
javaSrc.append("public class " + className + " implements MyServlet {\n");
javaSrc.append(" @Override\n");
javaSrc.append(" public void service(InputStream in, OutputStream out) throws Exception {\n");
javaSrc.append(" StringBuilder sb = new StringBuilder();\n");
//replaceはややこしいが" を \" に置換している。エスケープ
for (String line : jsp.split("\n")) {
javaSrc.append(" sb.append(\"" + line.replace("\"", "\\\"") + "\\n\");\n");
}
javaSrc.append(" String body = sb.toString();\n");
javaSrc.append(" String headers = \"HTTP/1.1 200 OK\\r\\n\" +\n");
javaSrc.append(" \"Content-Length: \" + body.getBytes(\"UTF-8\").length + \"\\r\\n\" +\n");
javaSrc.append(" \"Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n\";\n");
javaSrc.append(" out.write(headers.getBytes(\"UTF-8\"));\n");
javaSrc.append(" out.write(body.getBytes(\"UTF-8\"));\n");
javaSrc.append(" }\n");
javaSrc.append("}\n");
// Javaファイル出力
String fileName = className + ".java";
Files.writeString(Paths.get(fileName), javaSrc.toString());
return fileName;
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet path="/hello" class="HelloServlet"/>
<servlet path="/bye" class="ByeServlet"/>
</web-app>
サーブレット
HelloServlet.java
import java.io.*;
public class HelloServlet implements MyServlet {
@Override
public void service(InputStream in, OutputStream out) throws Exception {
String body = "<html><body><h1>Hello from HelloServlet!</h1></body></html>";
String headers = "HTTP/1.1 200 OK\r\n" +
"Content-Length: " + body.getBytes("UTF-8").length + "\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n\r\n";
out.write(headers.getBytes("UTF-8"));
out.write(body.getBytes("UTF-8"));
}
}
ByeServlet.java
import java.io.*;
public class ByeServlet implements MyServlet {
@Override
public void service(InputStream in, OutputStream out) throws Exception {
String body = "<html><body><h1>Goodbye!</h1></body></html>";
String headers = "HTTP/1.1 200 OK\r\n" +
"Content-Length: " + body.getBytes("UTF-8").length + "\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n\r\n";
out.write(headers.getBytes("UTF-8"));
out.write(body.getBytes("UTF-8"));
}
}
JSP
まだ変数やJavaコードには対応していないため、実質まだHTMLと何も変わらない
hello.jsp
<html>
<body>
<h1>Hello JSP World!</h1>
<p>This page is compiled at runtime</p>
</body>
</html>
ByeServlet.java
import java.io.*;
public class ByeServlet implements MyServlet {
@Override
public void service(InputStream in, OutputStream out) throws Exception {
String body = "<html><body><h1>Goodbye!</h1></body></html>";
String headers = "HTTP/1.1 200 OK\r\n" +
"Content-Length: " + body.getBytes("UTF-8").length + "\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n\r\n";
out.write(headers.getBytes("UTF-8"));
out.write(body.getBytes("UTF-8"));
}
}