[JAVA WAS] JAVA로 HTTP Request 읽기 문제 해결

Zoonmy·2024년 6월 26일

JAVA WAS

목록 보기
4/5

vanila java로 구현하던 was 프로젝트에서, http request를 읽다가 마주친 문제점들과 해결책들이 있어 정리해보고자 한다!

HTTP 요청 (HTTP Request)

  • 클라이언트서버에 작업을 요청하는 메세지

구성 요소

1) Request Line (요청 라인)

  • 메소드 (Method) 요청 유형 정의
    - GET, POST, DELETE, PUT, PATCH, ...
  • URL : 요청하는 리소스 경로
  • HTTP 버전 : 사용되는 HTTP 프로토콜 버전

2) Header (헤더)

  • 요청에 대한 메타데이터를 포함
    - Host, User-Agent, Accept, Content-Type , ...
  • key - value로 이루어져 있다.

3) Body (본문)

여기서 Http Request를 읽으면서 문제가 발생했던 부분이었다 ㅜㅅㅜ

  • 주로 POST, PUT 요청에 사용됨
  • 요청의 데이터를 포함
    - Ex) form 데이터, JSON, XML , ...

문제 상황

기존 코드

private BufferedReader br;

public HttpRequestReader(InputStream in) {
    try {
    	this.br = new BufferedReader(new InputStreamReader(in, Config.getEncoding()));
    } catch (UnsupportedEncodingException e) {
    	logger.error("[ERROR] 지원되지 않는 인코딩 에러 발생");
    	this.br = new BufferedReader(new InputStreamReader(in));
	}
}
...

 private String readBody(HttpRequestStartLine startLine, HttpRequestHeader header) throws IOException {
        if (startLine.getHttpMethod().equals(HttpMethod.GET)) {
            return NONE;
        }

        int length = Integer.parseInt(header.getValue("Content-Length"));

        char[] body = new char[length];
        br.read(body, 0, length);
        return new String(body);
   }
  • 기존에는 BufferedReader 을 통해 InputStream 값을 읽어오고 있었다.
    - 그래서 Request Body를 읽지 않는 GET 요청까지는 잘 구현했다(?) 고 생각했다...
  • POST 를 구현하면서, Header에 저장되어있는 length 값을 통해 Request Body 또한 읽을 수 있겠네~ 하고 구현하면서 에러가 나지 않아 행복한 코딩 시절..이었다 (행코행코~)

이미지 업로드.. 문제..

  • 이미지를 서버에 업로드 하게 되면서, 본격적인 헬코(헬코딩)가 시작되었다.

  • 그래서 이것을 해결!! 하면서 했던 나의 삽질을 정리해보겠다~!


해결티비

MDN 문서 정독

MDN 공식 DOCS

HTTP POST 메서드는 서버로 데이터를 전송합니다. 요청 본문의 유형은 Content-Type 헤더로 나타냅니다.

PUT과 POST의 차이는 멱등성으로, PUT은 멱등성을 가집니다. PUT은 한 번을 보내도, 여러 번을 연속으로 보내도 같은 효과를 보입니다. 즉, 부수 효과(side effect)가 없습니다.

POST 요청은 보통 HTML 양식을 통해 서버에 전송하며, 서버에 변경사항을 만듭니다. 이 경우의 콘텐츠 유형(Content-Type)은 <form> 요소의 enctype 특성이나 <input>, <button> 요소의 formenctype 특성 안에 적당한 문자열을 넣어 결정합니다.

application/x-www-form-urlencoded: &으로 분리되고, "=" 기호로 값과 키를 연결하는 key-value tuple로 인코딩되는 값입니다. 영어 알파벳이 아닌 문자들은 percent encoded 으로 인코딩됩니다. 따라서, 이 content type은 바이너리 데이터에 사용하기에는 적절치 않습니다. (바이너리 데이터에는 use multipart/form-data 를 사용해 주세요.)_
multipart/form-data
text/plain

여기서 유심히 봐야 할 키워드는?!

  • 바이너리 데이터에는 use multipart/form-data 를 사용해 주세요...!

multipart/form-data가 뭘까?

  • 파일 업로드와 함꼐 여러 종류 데이터를 서버로 POST로 전달하기 위해 사용되는 `콘텐츠 타입
  • 파일 업로드와 같은 대량의 바이너리 데이터 를 전송할 때 유용하다.
  • multipart/form-data 요청은 여러 파트로 나뉘며, 각 파트는 각각의 헤더와 본문을 가진다.
POST /upload HTTP/1.1
Host: www.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 5000

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="field1"

value1
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="field2"

value2
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain

... (file content) ...
------WebKitFormBoundary7MA4YWxkTrZu0gW--

오케이! 그렇다면 Request Body는 대량의 바이너리 데이터 로 온다는 것을 알았다!

그렇다면 이제 발생하는 문제에 대한 원인을 좁힐 수 있었다.

BufferedReader .. 너 바이트 읽을 수 있어???

Java Docs

Java Docs

BufferdReader 특성

  • java.io 패키지에 포함된 클래스 중 하나.
  • 주로 문자 입력 스트림에서 텍스트를 효율적으로 읽을 수 있도록 버퍼링 기능 제공
  • 파일이나 네트워크 소켓과 같은 입력 소스에서 텍스트 데이터 를 읽는 데 사용

Trouble Shooting!

  • Multipart/Form-DataBinary 타입으로 서버에게 전달된다.
  • 하지만 현재 BufferedReader로 해당 Request Body를 읽고 있어, Encoding 오류 문제가 계속 발생하였다.
  • BufferedReader텍스트 를 읽기 위해 사용되는 io 클래스이며, 우리는 Byte 단위로 읽어야 하기 때문에, BufferedReader는 적합하지 않다!

코드 개선

private BufferedInputStream bis;

public HttpRequestReader(InputStream in) {
	bis = new BufferedInputStream(in);
}

...


 private byte[] readBody(HttpRequestStartLine startLine, HttpRequestHeader header) throws IOException {
        if (startLine.getHttpMethod().equals(HttpMethod.GET)) {
            return NONE;
        }
        int contentLength = Integer.parseInt(header.getValue("Content-Length"));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;
        int totalBytesRead = 0;

        while (totalBytesRead < contentLength &&
                (bytesRead = bis.read(buffer, 0, Math.min(BUFFER_SIZE, contentLength - totalBytesRead))) != END_OF_LINE) {
            baos.write(buffer, 0, bytesRead);
            totalBytesRead += bytesRead;
        }
        return baos.toByteArray();
    }
  • BufferedInputStream 을 이용해서 byte 단위로 Request Body를 읽어서 성공적으로 이미지 파일을 업로드 할 수 있었다!

사실 공식 문서를 조금만 찾아봐도 해결할 수 있는 문제였지만, 해당 문제로 거의 1주일은 넘게 골머리를 앓았던 것 같다...
왜냐하면 BufferedReader이나 Scanner 외에 io 클래스는 사용해 본 적이 없어서 무지했던 것 같다.
하지만, 이번 Trouble Shooting을 처리하면서, 오류가 생기거나 모르는 것이 있으면, 다른 사람의 기술 블로그나 코드를 보는 것 보다, 공식 문서를 보고 개선해 나가는 것이 나의 학습과 성장에 있어서 더욱 좋은 방향이라고 느끼게 되는 계기가 되어서 너무 뿌듯하다!

profile
열시미 해야쥐

0개의 댓글