Javaでフレームワーク(Httpサーバ)を作成してみる

#### Javaでフレームワークをなるべくスクラッチで作成してみました
フレームワーク自体を作成すれば、自由度が少しでも広がると思い作成してみました。

こちらが今回作成したコードになります。
https://github.com/yuki-yanagawa/FrameWorkTest

## 目次
[1. 概要](#概要)
[2. エントリポイント](#エントリポイント)
[3. レシーバの内容](#レシーバの内容)
[4. キューについて](#キューについて)
[5. タスクマネージャの内容](#タスクマネージャの内容)
[6. リクエストヘッダの解析](#リクエストヘッダの解析)
[7. 処理の実行](#処理の実行)
[8. 最後に](#最後に)

##  概要
大きな流れとしまして、「RecevierがHttpリクエストを受け取る -> QueueにHttpリクエストをためる -> TaskManagerがQueueからHttpリクエストを受け取り、処理を実行する」形になります。

リクエストヘッダを解析し、以下定義ファイルを元に実行する処理を決定します。
>メソッド[空白]URI[空白]処理するファイルのパス と記載するようにします。

```conf:def/router
GET  /         testframe.gui.controller.IndexHtmlController.main
GET  /main.js  testframe.gui.controller.JavaScriptFileController.main
```
こちらに記載していないURIのリクエストがきた場合は現状対応できておりません。
クライアントに404をリターンする形になります。

以下より、具体的な内容を記載します。



## エントリポイント

Propertiesファイルを読み込み、TaskManagerのスレッド、HTTPRecvierのスレッドを走らせる形になります。(Thread.sleep(100)は暫定的に入れました。。。)

```java:EntryPoint.java
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の中身を取得する処理になります。

```java:HttpReciver.java
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パターンを意識して作成しました。

```java:HandleThreadQueueManager.java
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を実行する形になります。

```java:ReciverHandleThreadTaskManager.java
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」から実行する処理を選択します。

```java:HttpHandleUtil.java
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にして渡すように現在しております。

```conf:def/router
GET  /         testframe.gui.controller.IndexHtmlController.main
GET  /main.js  testframe.gui.controller.JavaScriptFileController.main
```
```java:HttpHandleUtil.java
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ファイルをリターンするイメージです。
```java:IndexHtmlController.java
public class IndexHtmlController extends ControllerFrame {
	public HttpResFrame main() {
		return html("frontparts/html/index.html");
	}
}
```

```java:ControllerFrame.java
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);
		}
	} 
}
```

## 最後に
記事を最後まで読んで頂き有難うございました。
ご指摘等御座いましたら、頂けると幸いです。