본문 바로가기
Today I learned

HTTP request Parser 코드 분석(JAVA)

by soheemon 2019. 4. 22.

http://www.hanbit.co.kr/store/books/look.php?p_code=E1064947038

 

웹 프로그래머를 위한 서블릿 컨테이너의 이해

JSP/서블릿은 웹 개발에 많이 사용하고 있지만 서블릿을 제대로 알고 사용하는 개발자는 많지 않습니다. 이는 대부분의 웹 개발자가 웹 애플리케이션 서버에서 제공하는 실행 환경 위에서 동작하는 코드를 작성하기 때문입니다. 그래서 웹 관련 문제가 발생하였을 때, 문제의 원인조차 파악하지 못하는 경우가 많아졌습니다. 이 책은 웹을 개발하면서 겪어 봤을 서블릿 컨테이너 관련 문제점과 궁금증을 속 시원하게 풀어줍니다. 서블릿의 동작원리, 개발 시 겪게 되는 문제

www.hanbit.co.kr

위 책에서 나오는 예제소스입니다.

주요 keyPoint는 Body length를 헤더의 ContentLength를 참고하는것.

Todo. 해당 코드를 분석한 후 C로 옮깁시다.

작년에 고생했었던 애증의 HTTP Server를 깨부숩시다

vim따윈 쓰지않는다 VScode쓸거다

public class Server2 {
	public static void main(String[] args) throws IOException {
		Server2 server = new Server2();
		server.boot();
	}
	public static final byte CR = '\r';
	public static final byte LF = '\n';
	private ServerSocket serverSocket;
	
	private void boot() throws IOException {
		serverSocket = new ServerSocket(8000);
		Socket socket = serverSocket.accept();
		InputStream in = socket.getInputStream();
		
		int oneInt = -1;
		byte oldByte = (byte)-1;
		StringBuilder sb = new StringBuilder();
		int lineNumber = 0;
		
		boolean bodyFlag = false;
		String method = null;
		String requestUrl = null;
		String httpVersion = null;
		int contentLength = -1;
		int bodyRead = 0;
		
		List<Byte> bodyByteList = null; //message Body에 해당
		Map<String, String> headerMap = new HashMap<String, String>();
		
		while(-1 != (oneInt = in.read())) {// TCP단에서 커넥션이 끊길경우(-1반환) stream에서 읽어오기를 중단한다.
			byte thisByte = (byte)oneInt;
			//message Body에 해당.
			if(bodyFlag) {
				bodyRead++;
				bodyByteList.add(thisByte);
				if(bodyRead >= contentLength) {
					break;
				}
			}
			else {
				if(thisByte == Server2.LF && oldByte == Server2.CR) { //한행의 마지막 체크
					String oneLine = sb.substring(0, sb.length()-1);
					lineNumber++;
					//첫번째 행. HTTP MEthod, 요청 URL, 버전을 알아낸다. 예를들어..
					//GET / HTTP/1.1 첫번째라인의 첫번째 글자부터 ~ 첫번째 공백까지는 'Method'에 해당하게 된다.
					if(lineNumber == 1) {
						int firstBlank = oneLine.indexOf(" ");
						int secondBlank = oneLine.lastIndexOf(" ");
						method = oneLine.substring(0, firstBlank); //GET OR POST에 해당.
						requestUrl = oneLine.substring(firstBlank + 1, secondBlank); // 첫번째 공백 다음글자 + 두번째 공백 까지는 Url에 해당.
						httpVersion = oneLine.substring(secondBlank + 1); // httpVersion은 두번째 공백을 시작으로 마지막 글자까지.
					}
					else {
						//Message Body가 시작되기 전에 있는 빈공백줄을 읽음. 즉, header가 끝났음을 의미.
						if(oneLine.length() <= 0) {
							bodyFlag = true;
							if("GET".equals(method)) {
								//GET방식이면 Body가 없으므로, read를 멈춘다.
								break;
							}
							String contentLengthValue = headerMap.get("Content-Length");
							if(contentLengthValue != null) {
								contentLength = Integer.parseInt(contentLengthValue.trim());
								bodyFlag = true;
								bodyByteList = new ArrayList<Byte>();
							}
							continue; // content-length값을 얻어 온 후, '
						}
						//header를 key와 value로 읽어서 headerMap에 넣기.
						int indexOfColon = oneLine.indexOf(":");
						String headerName = oneLine.substring(0, indexOfColon);
						String headerValue = oneLine.substring(indexOfColon + 1);
						headerMap.put(headerName, headerValue);
					}
					sb.setLength(0);//읽어온 한줄에대한 작업이 끝났다.
				}
				else { // 그 행의 마지막이 아니라면,(LF&CR) string builder에 붙인다.
					sb.append((char)thisByte);
				}
			}
			oldByte = (byte)oneInt;
		}//while
		in.close();
		socket.close();
		
		System.out.printf("METHOD: %s REQ: %s HTTP VER. %s\n", method, requestUrl, httpVersion);
		System.out.println("headerList");
		Set<String> keySet = headerMap.keySet();
		Iterator<String> keyIter = keySet.iterator();
		while(keyIter.hasNext()) {
			String headerName = keyIter.next();
			System.out.printf("Key :%s Value:%s\n",headerName, headerMap.get(headerName));
		}
		if(bodyByteList != null) {
			System.out.print("Message Body -->");
			for(byte oneByte : bodyByteList) {
				System.out.print(oneByte);
			}
			System.out.println("<--");
		}
		System.out.println("end of HTTP Messate.");
	}//boot()
}//Class

 

 

댓글