HttpServletRequest

sally·2022년 3월 31일
0

HTTP

목록 보기
1/1

일요일에 1~3단계를 몰아서 한 작업이 티가 났다. ㅎㅎ
나중에 고치려고 했는데, 메서드 일부만 분리 해놓고 마무리 했었고,..
미션 6까지 가니, UrlMapper 내에서 고쳐야 할 부분이 많이 있었다.

그나마 RequestHeader 부분을 메서드로 나눠놔서 고치더라도 이동만 했었던 과정을 생각하면, 항상 시작할 때에 공을 들이고, 여유를 가지는게 중요하다고 느껴진다.

그리하여 페어와 했던 1단계 리팩토링 작업하면서
BufferedReader를 받게한, POST body 처리 부분을 앞단에서 완성하여 Request 객체로 반환시키게 했었다.

public static Request from(BufferedReader bufferedReader) {
        RequestWriter requestWriter = new RequestWriter();
        try {
            String line = bufferedReader.readLine();
            requestWriter.messages.add(line);
            log.debug(line);
            while (!line.equals("")) {
                line = bufferedReader.readLine();
                requestWriter.messages.add(line);
            }
            Request request = new Request(requestWriter.messages);
            request.of(bufferedReader);
            return request;
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return new Request();
    }

문제는 기존에 작성해 뒀던 테스트 코드에서 발생했다.
기존에 전달 받은 BufferedReader를 통해 뒷단에서 POST 요청시 메시지 바디를 반환하여 컨트롤러로 전달하는 방법으로 인해서 아래와 같이 getUser() 로 전달하던 파라미터를 BufferedReader를 통해 생성되는 Request 객체로 반환해야 되다보니, 생각보다 복잡해졌다.
테스트 코드는 일단 삭제해서 4단계 PR을 보냈다. (페어, 고생많았습니다. ㅠ)
🙂 나는 맑고 깨끗해졌다고 표현한다....클린코드...

@DisplayName("회원가입 요청시 새로운 사용자는 회원가입 진행되어 리다이렉트 응답코드 302를 확인한다")
    @Test
    void check_status_302_when_new_user_join() {
        HttpResponse actual = userController.join(getUser(), getHttpResponse());

        assertThat(actual.getHttpStatus()).isEqualTo(HttpStatus.FOUND);
    }

소켓을 통해 InputStream으로 요청 메시지를 읽는데, 요청마다 달라지는 상황에서, POST 요청에 대한 요청 메시지 바디 처리를 어떻게 하는지 궁금해졌다.

먼저 오늘 리팩토링 결과로는 기존의 테스트 코드 변경시에도, 완전히 분리되지 않은 InputStream 부분 때문에 불편했었다.


HttpServlet 실습 코드

  • 아래의 코드는 인프런, 김영한 님의 MVC1 강의 실습 내용중 일부 입니다.
@WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-body-text")
public class RequestBodyStringServlet extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws
		ServletException,
		IOException {
		// HTTP 메시지 바디 데이터는 InputStream 통해 읽는다.
		ServletInputStream inputStream = request.getInputStream();
		// byte 코드를 String으로 변환 + byte <-> String 변환시 항상 인코딩 정보를 알려줘야 한다
		String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);  // Spring제공 Utility
		System.out.println("messageBody = " + messageBody);

		response.getWriter().write("200 OK");
	}
}

POST의 body는 전달받은 HttpServletRequest의 getInputStream()을 통해 HTTP 메시지 바디를 읽었다.
그래서 HttpServletRequest는 어떻게 읽어오는지 보는데

  • 먼저 구현 클래스는 Request로 Connector를 받아 생성되고 있었는데, 코드만 보면 이해하기가 어려웠었다. 새로 생성한 inputStream 이어서, 어떻게 내가 작업중인 웹서버의 socket의 inpustream으로 읽어오는지 😭



  • 아래 사이트를 참고하니, 일단 Content-Length를 확인하면서 읽는게 안정적이라는 생각으로 리팩토링을 작업했다.
    자바 InputStream 주의사항

리팩토링

	public static Request from(BufferedReader bufferedReader) {
		HttpMessage httpMessage = new HttpMessage();
		try {
			String line = bufferedReader.readLine();
			httpMessage.add(line);
			log.debug(line);
			readRequestMessage(bufferedReader, httpMessage, line);
			Request request = new Request(httpMessage.getMessage());
			readBody(bufferedReader, request);
			return request;
		} catch (IOException e) {
			log.error(e.getMessage());
		}
		return new Request();
	}
    
    // 추가된 로직
    private static void readBody(BufferedReader bufferedReader, Request request) throws IOException {
		if (request.isPostMethod() && request.contentLength() > 0) {
			String body = IOUtils.readData(bufferedReader, request.contentLength());
			request.writeLine(String.format("body: %s", body));
		}
	}

etc

추가로 질문을 올렸었다.
HTTP 스타트라인의 버전 정보를 요청과 응답으로 연결지어서 보내는지 의문이 들었었다. 일단은 요청온대로 버전을 응답으로 담아 보내주게 했었다.
이 점도, UrlMapper로 Request 객체를 넘겨주고서, HttpResponse를 생성하는 과정이 뒤로 밀리게 한 점도 있었다.
불문명한 내용들이 조금씩 뒤로 미루니 코드도 객체로서 역할과 책임을 분명하게 갖지 못하게 됐던 것 같다.

  • 요청받은 버전 정보를 서버가 맞춰줄까?
    • 서버는 버전 정보대로 네트워크 변경은 가능하기 어려울것 같고, 내 웹서버는 그 정도까지는...
  • 하지만 클라이언트가 요청한다면?

모르고 보면 코드도 어려운데, 호눅스님 덕분에 코드를 좀더 잘 볼 수 있었던 것 같다. 실수와 고민의 과정도 성장의 과정이 될 기회라고 마인드 셋팅 한다.🙃

상단에 올린 이미지에서 new Request(Connector connector); 로 Connetor를 따라가 보니

  • default로는 HTTP/1.1 이다.
  • ProtocolHandler.create(protocol, apr)
  • protocolHandler로 전달
  • 그것도 아니면 최종적으로 protocolHandlerClassName 로 요청받은 protocol (HTTP 버전 정보)를 담게 했다.

ProtocolHandler.create(protocol, apr) 를 보면

AJP/1.3 외에는 HTTP/1.1이고, ProtoclHandler로 타입 캐스팅 되는 게 보인다.

AJP/1.3

참고 사이트에서 발췌한 글입니다.
아파치-톰캣연동, AJP

AJP란? Apache JServ Protocol

웹서버 뒤에 있는 와스로 부터 웹서버에서 받은 요청을 와스로 전달해주는 프로토콜이다.

  • 아파치와 톰캣을 연동하기 위해서는 AJP를 통해 서로 통신을 한다.
    아파치는 이를 사용하여 80포트로 들어오는 요청은 자신이 받고, 이 요청중 서블릿을 필요로 하는 요청은 톰캣에게 전달하여 처리한다. (httpd.conf 의 JkMount설정 )
  • 해당 프로토콜(ajp)는 다양한 WAS에서 지원한다. ex) 아파치, 톰캣, 제우스, 웹로직, 웹스피어 등...
    출처: https://cheershennah.tistory.com/142 [Today I Learned. @cheers_hena 치얼스헤나]
profile
sally의 법칙을 따르는 bug Duck

0개의 댓글