初めに
前回JavaでHTTPサーバを実装しました。
https://qiita.com/earthen94/items/4d4906fbb1b1f93eedc9
今回はこれを拡張し、簡単なJAVAサーブレットを動かせるようにしました。
見通しを良くする為にかなり短くしています。
POSTやパラメータ取得は次回に譲ります。
実行
javac MiniContainer.java MyServlet.java HelloServlet.java ByeServlet.java
java MiniContainer
ブラウザ上で以下を入力
http://localhost:8081/hello
http://localhost:8081/bye
実装部分
サーブレットのインターフェース
どんなサーブレットでもこのメソッドを実装するとコンテナ側から型を気にしなくてよくなる。
MyServlet.java
import java.io.*;
public interface MyServlet {
void service(InputStream in, OutputStream out) throws Exception;
}
コンテナ
MiniContainer.java
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;
public class MiniContainer {
private static final Map<String, MyServlet> routes = new HashMap<>();
public static void main(String[] args) throws Exception {
// ユーザが作ったServletクラスをロードして登録
// Tomcatであればここはweb.xmlに登録する
addServletFromClass("/hello", "HelloServlet");
addServletFromClass("/bye", "ByeServlet");
// ポート8081で待ち受ける。IPアドレスは内部的にOSから取得するので不要
ServerSocket server = new ServerSocket(8081);
System.out.println("Running on http://localhost:8081/");
// ここを無限ループにしないと1リクエストしか受け付けられない
// (一回ブラウザから開いたらサーバーが閉じられてしまう)
while (true) {
Socket client = server.accept();//ポート8081に来るTCP接続を待つ
handleClient(client);
}
}
// クラス名からユーザのサーブレットをロードして登録
private static void addServletFromClass(String path, String className) throws Exception {
Class<?> clazz = Class.forName(className); // クラスをロード
// サーブレットのインスタンスを作成
// clazz.getDeclaredConstructor()は引数無しコンストラクタ
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(); // サーブレットの中でoutの中に詰めたHTTPヘッダ及びHTMLを送信
client.close();
}
}
サーブレット
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"));
}
}