Javaでフレームワークをなるべくスクラッチで作成してみました
フレームワーク自体を作成すれば、自由度が少しでも広がると思い作成してみました。
こちらが今回作成したコードになります。
https://github.com/yuki-yanagawa/FrameWorkTest
目次
1. 概要
2. エントリポイント
3. レシーバの内容
4. キューについて
5. タスクマネージャの内容
6. リクエストヘッダの解析
7. 処理の実行
8. 最後に
概要
大きな流れとしまして、「RecevierがHttpリクエストを受け取る -> QueueにHttpリクエストをためる -> TaskManagerがQueueからHttpリクエストを受け取り、処理を実行する」形になります。
リクエストヘッダを解析し、以下定義ファイルを元に実行する処理を決定します。
メソッド[空白]URI[空白]処理するファイルのパス と記載するようにします。
GET / testframe.gui.controller.IndexHtmlController.main
GET /main.js testframe.gui.controller.JavaScriptFileController.main
こちらに記載していないURIのリクエストがきた場合は現状対応できておりません。
クライアントに404をリターンする形になります。
以下より、具体的な内容を記載します。
エントリポイント
Propertiesファイルを読み込み、TaskManagerのスレッド、HTTPRecvierのスレッドを走らせる形になります。(Thread.sleep(100)は暫定的に入れました。。。)
public class EntryPoint {
public static void main(String[] args) {
try {
//Properties File read
PropFileReader.refresh();
Thread.sleep(100);
//Task Manager Start
TaskManagerEntry.taskManagerStart();
Thread.sleep(100);
//HttpReciver Start
HttpReciver reciver = new HttpReciver();
reciver.httpReciverStart();
} catch (Exception e) {
e.printStackTrace();
}
}
}
レシーバの内容
ServerSocketを利用し、クライアントからの要求を待ちます。リクエストを受け取ったら、HandleThreadQueueManagerのQueueにリクエストを追加します。
HandleThreadQueueManagerはシングルトンパターンで定義し、HttpReciverでQueueにため、TaskManagerでQueueの中身を取得する処理になります。
public class HttpReciver {
........
public void httpReciverStart() throws IOException {
Thread.currentThread().setName("HttpReciver");
while(serverExeuteFlg) {
try {
Socket socket = svrSock_.accept();
HandleThreadQueueManager.getInstacnce().addHandleThread(new HttpHandleThread(socket));
} catch(InterruptedException e) {
Logger.warn(Thread.currentThread().getName(), "HandleThread Add Queue Failed...");
} catch(IOException e) {
Logger.warn(Thread.currentThread().getName(), "Server Socket Accept Failed...");
} catch(Error e) {
Logger.error(e.getMessage());
serverExeuteFlg = false;
}
}
closeServerSocket();
}
}
キューについて
RecvierがaddHandleThreadでリクエストをQueueをためる。Queueのサイズが設定より大きくなる場合は待つ。
TaskManagerがgetHandleThreadでQueueからリクエストを受け取り、リクエストを解析する。Queueが空の場合は待つ。
Producer-Consumerパターンを意識して作成しました。
public class HandleThreadQueueManager {
.....
public synchronized void addHandleThread(HandleThread e) throws InterruptedException {
while (threadCountMax_ < queue_.size()) {
wait();
}
queue_.add(e);
notifyAll();
}
public synchronized HandleThread getHandleThread() throws InterruptedException {
while(queue_.size() <= 0) {
wait();
}
HandleThread handleThread = queue_.poll();
notifyAll();
return handleThread;
}
}
タスクマネージャの内容
内部クラスのReciverHandleThreadMainがQueueからリクエストを取得する形になります。
TaskManagerがリクエストを解析すると記載しましたが、詳細はQueueに貯めていたHttpHandleThreadを実行する形になります。
public class ReciverHandleThreadTaskManager implements TaskManager {
@Override
public void execute() {
ReciverHandleThreadMain reciverHandleThreadMain = new ReciverHandleThreadMain();
reciverHandleThreadMain.start();
}
class ReciverHandleThreadMain extends Thread {
@Override
public void run() {
Thread.currentThread().setName("ReciverHandleThreadMain");
while(true) {
HandleThread handleThread = trytakeFromHandleThreadQueue();
if(handleThread != null) {
handleThread.start();
}
}
}
private HandleThread trytakeFromHandleThreadQueue() {
Logger.info(Thread.currentThread().getName(),"try take start......");
HandleThread handleThread = null;
try {
handleThread = HandleThreadQueueManager.getInstacnce().getHandleThread();
} catch(InterruptedException e) {
Logger.error(Thread.currentThread().getName(), "Take HandleThread From Queue");
}
Logger.info(Thread.currentThread().getName(),"try take end......");
return handleThread;
}
}
}
リクエストヘッダの解析
簡単な内容と致しましては、リクエストヘッダは「\r\n」を基にパラメータの項目毎に分解できるので分解し、先頭のリクエストラインは「空白」を基にメソッド、URIに分解できるので分解しております。
取得した「メソッド」、「URI」を基に「def/router」から実行する処理を選択します。
public class HttpHandleUtil {
private static int recvBufSize = 2048;
private static String serverName;
public static int getRecvBufize() {
return recvBufSize;
}
public static String httpRequestAnalizeAndCreateResponse(String httpRequest) throws HttpCreateResponseException {
String retStr = "";
String[] httpRequestLine = httpRequest.split("\r\n");
String httpRequestHeader = httpRequestLine[0];
String[] httpRequestHeaderLine = httpRequestHeader.split(" ");
if(httpRequestHeaderLine.length < 2) {
throw new HttpCreateResponseException("CreateResponsError");
}
String requestMethod = httpRequestHeaderLine[0].trim();
String requestData = httpRequestHeaderLine[1].trim();
String excludeHeaderHttpRequest = httpRequest.substring(httpRequestHeader.length());
Map<String,String> requestParameterMap = new HashMap<>();
requestParameterMapConverter(httpRequestLine,requestParameterMap);
Map<String,Object> routesrFileMapAllList = RouterFileReader.getInstance().getRouterFileMap();
Map<String,String> routerFileMap = (Map<String,String>)routesrFileMapAllList.get(requestMethod);
String actionClassPath = routerFileMap.get(requestData);
if(actionClassPath == null || actionClassPath.trim().equals("")) {
Logger.warn(Thread.currentThread().getName(), "this web api is not define : " + requestData);
return HttpResponseHeader.createResponseHeaderBad();
}
if(ControllerCache.getInstance().existData(actionClassPath)) {
Logger.info(Thread.currentThread().getName(), actionClassPath + " is From Cashe");
HttpResFrame httpResFrame = ControllerCache.getInstance().getData(actionClassPath);
return httpResFrame.createResponse();
}
if(requestMethod.toUpperCase().equals("POST")) {
//Web API
int dataLength = 0;
dataLength = requestParameterMap.get("CONTENT-LENGTH") != null ? Integer.parseInt(requestParameterMap.get("CONTENT-LENGTH")) : 0;
if(dataLength == 0) {
try {
HttpResFrame httpResFrame = actionCalssCallHandler(actionClassPath);
return httpResFrame.createResponse();
} catch(Exception e) {
e.printStackTrace();
return HttpResponseHeader.createResponseHeaderInternalServerError(e.getMessage());
}
} else {
String requestPrameterData = excludeHeaderHttpRequest.trim().substring(excludeHeaderHttpRequest.trim().length() - dataLength);
Map<String,Object> reqMap = null;
try {
requestPrameterData = URLDecoder.decode(requestPrameterData, "UTF-8");
} catch (UnsupportedEncodingException e) {
requestPrameterData = "";
}
if(requestPrameterData.trim().equals("")) {
reqMap = new HashMap<>();
} else {
reqMap = createReqParamMap(requestPrameterData);
}
try {
HttpResFrame httpResFrame = actionCalssCallHandler(actionClassPath, reqMap);
return httpResFrame.createResponse();
} catch(Exception e) {
e.printStackTrace();
return HttpResponseHeader.createResponseHeaderInternalServerError(e.getMessage());
}
}
} else if(requestMethod.toUpperCase().equals("GET")) {
try {
HttpResFrame httpResFrame = actionCalssCallHandler(actionClassPath);
if(!ControllerCache.getInstance().existData(actionClassPath)) {
Logger.info(Thread.currentThread().getName(), actionClassPath + " join Cashe");
ControllerCache.getInstance().addCacheData(actionClassPath, httpResFrame);
}
return httpResFrame.createResponse();
} catch(Exception e) {
e.printStackTrace();
return HttpResponseHeader.createResponseHeaderInternalServerError(e.getMessage());
}
}
return retStr;
}
}
処理の実行
取得した「メソッド」、「URI」を基に「def/router」から実行する処理を実行します。
「GET」メソッドでURIが「/」の場合、testframe.gui.controller.IndexHtmlController.mainの処理を実行します。
callFunctionの第一引数のpathが「testframe.gui.controller.IndexHtmlController.main」、
第二引数以降の内容はHttpリクエストにボディが存在した場合、mapにして渡すように現在しております。
GET / testframe.gui.controller.IndexHtmlController.main
GET /main.js testframe.gui.controller.JavaScriptFileController.main
public class RouterContainer {
public static <T> T callFunction(String path, Object... arglist) throws Exception {
String[] pathList = path.split("\\.");
String method = pathList[pathList.length - 1];
path = path.substring(0,path.length() - method.length() - 1);
Class<?> actionClass = (Class<?>)Class.forName(path);
Object action = actionClass.getConstructor().newInstance();
Method[] methods = actionClass.getDeclaredMethods();
for(Method m : methods) {
if(m.getName().equals(method)) {
return (T)m.invoke(action, arglist);
}
}
return null;
}
}
処理が呼び出される。
この処理はhtmlファイルをリターンするイメージです。
public class IndexHtmlController extends ControllerFrame {
public HttpResFrame main() {
return html("frontparts/html/index.html");
}
}
public abstract class ControllerFrame {
public HttpResFrame html(String path) {
try(FileInputStream fi = new FileInputStream(path)){
File fileData = new File(path);
long filesize = Files.size(fileData.toPath());
byte[] readfile = new byte[(int)filesize];
fi.read(readfile);
String header = HttpResponseHeader.createResponseHeaderSuccess(ContentType.HTML,(int)filesize);
header = header + "\r\n";
String body = new String(readfile,"UTF-8");
return new HttpResFrame(header, body, ContentType.HTML);
} catch(IOException e) {
String header = HttpResponseHeader.createResponseHeaderInternalServerError();
return new HttpResFrame(header, "", ContentType.HTML);
}
}
}
最後に
記事を最後まで読んで頂き有難うございました。
ご指摘等御座いましたら、頂けると幸いです。