Java - 14. HTTP 프로토콜

갓김치·2020년 10월 16일
0

고급자바

목록 보기
37/47

HTTP

  • HyperText Transfer Protocol
  • WWW 상에서 정보를 주고받을 수 있는 프로토콜
  • 주로 HTML문서를 주고받는 데에 쓰임
  • TCPUDP를 사용하며 80번 포트 사용
  • 클라이언트와 서버 사이에 이루어지는 요청/응답(request/response)프로토콜
  • HTTP를 통해 전달된 자료는 http:로 시작하는 URL로 조회 가능

HTTP 특징

1. 비연결성(Connectionless) 프로토콜

  • 클라이언트와 서버가 연결을 맺은 후, 클라이언트 요청에 대해 서버가 응답을 하고나면, 맺고 있었던 연결을 종료시키는 것을 말한다.
  • 잦은 연결/해제에 따른 오버헤드를 줄이고자 HTTP1.1에서는 KeepAlive 속성을 사용할 수 있다.
    • KeepAlive: 지정된 시간동안 서버와 클라이언트 사이에 패킷 교환이 없는 경우, 상대방의 상태를 확인하기 위해 패킷을 주기적으로 보내고, 이에 대한 응답이 없으면 접속을 종료

2. 상태가 없는(stateless) 프로토콜

  • 비연결성으로 인해 서버는 클라이언트를 식별할 수가 없는 상황
    • 보내고 끊고 보내고 끊고 하다보니 아까 그놈인지 새로운 놈인지 식별 불가

3. 일반적으로 TCP/IP 통신 위에서 동작하며 기본포트는 80번

HTTP 프로토콜 구조

Connect → Request → Response → Close

요청 메시지응답메시지
Request LineStatus Line
GET /restapi/v1.0 HTTP/1.1
- GET: 전송방식 GET VS. POST
- /restapi/v1.0: 요청하고자하는 resource(path, file 정보)
- HTTP/1.1: 프로토콜 방식, 버전
HTTP/1.1 200 OK
- HTTP/1.1: 프로토콜 방식, 버전
- 200: 응답코드
- OK: 상태
HeaderHeader
Accept: application/json
Authorization: Bearer UExBMDFUMDRQV1Mw...
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close
Empty LineEmpty Line
 
BodyBody
- 보내거나 받고자 하는 실제 데이터를 의미
- 포스트방식에만 존재 (GET등의 경우에는 요청시 생략 가능)
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello, World!
</body>
</html>

HTTP 요청 메서드

  • apache api - class HTTPServlet 메서드 doGet, doPost... 참고
  • GET: 이미 존재하는 자원(리소스)에 대해 요청함
  • POST: 새로운 자원 생성 요청
  • PUT: 이미 존재하는 자원 변경 요청
  • DELETE: 기존 자원 삭제 요청
  • HEAD: 기존 자원의 헤더 정보 요청
  • OPTIONS: 서버 옵션들을 확인하기 위한 요청, CORS에서 사용
  • TRACE: 클라이언트가 보낸 요청을 그대로 반환
  • CONNECT: 프록시 터널링을 위해 예약된 메서드

HTTP 상태 코드

교재 P.115 참고

  • 정보 응답 100~
    • 100 Continue
    • 101 Swtiching Protocol
    • 102 Processing
    • 103 Early Hints
  • 성공 응답 200~
    • 200 OK
    • 204 No Content
    • 205 Reset Content
    • 206 Partial Content
  • 리다이렉션 메시지 300~
    • 301 Moved Permanently
    • 303 See Other
    • 304 Not Modified
  • 클라이언트 에러 응답 400~
    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
    • 405 Method Not Allowed
    • 409 Conflict
  • 서버 에러 응답 500~
    • 500 Internal Server Error
    • 501 Not Implemented
    • 502 bad Gateway
    • 503 Service Unavailable

기타

MIME 타입

  • Multipurpose Internet Mail Extensions
  • 전자 우편을 위한 인터넷 표준 포맷
  • 전자우편은 7비트 ASCII 문자를 사용하여 전송되기 때문에, 8비트 이상의 코드를 사용하는 문자나 이진 파일들은 MIME 포맷으로 변환되어 SMTP로 전송된다.
    • 실질적으로 SMTP로 전송되는 대부분의 전자 우편은 MIME 형식이다.
      -MIME 표준에 정의된 content types은 HTTP와 같은 통신 프로토콜에서 사용되며, 점차 그 중요성이 커지고 있다.

예제: SimpleHTTPServer.java

  • 준비: Run Configuration - Arguments - test.html 80 UTF-8 설정
  • 코드에 나와있는 순서는 실행 순서 (코딩 순서 아님)
public class SimpleHTTPServer {

private static final Logger LOGGER = Logger.getLogger("SimpleHTTPServer");

private final byte[] content; // String data를 받아 인코딩에 맞게 byte[]배열된 데이터
private final byte[] header; String header가 인코딩에 맞게 byte[]배열로 변환됨
private final int port;
private final String encoding;

// String data를 받는 생성자
public SimpleHTTPServer(String data, String encoding, String mimeType, int port) 
	throws UnsupportedEncodingException 
{
	this(data.getBytes(encoding), encoding, mimeType, port);
    // String data를 인코딩에 맞는 byte[]배열로 변환해주는 생성자 호출
}

// String data를 인코딩에 맞는 byte[]배열로 변환해주는 생성자
public SimpleHTTPServer(byte[] data, String encoding, String mimeType, int port) {
  this.content = data;
  this.port = port;
  this.encoding = encoding;
  // Response Header:
  // 모든 데이터는 줄 끝마다 2바이트 CR LF ('\r\n')를 사용하여 플레인 텍스트(ASCII) 인코딩을 통해 송신된다 (교재 p.113)
  String header = "HTTP/1.0 200 OK\r\n"
      + "Server: SimpleHTTPServer 1.0\r\n"
      + "Content-length: " + this.content.length + "\r\n"
      + "Content-type: " + mimeType + "; charset=" + encoding + "\r\n\r\n";
  this.header = header.getBytes(Charset.forName("US-ASCII"));	
}

  // 9. main()에서 server.start()되면 일어나는 일 
  public void start() { // 내부적으로 AutoClosable 구현되어있음
    try (ServerSocket server = new ServerSocket(this.port)) {
    // 10. 로그
    LOGGER.setLevel(Level.INFO);
    LOGGER.info("Accepting connections on port " + server.getLocalPort());
    LOGGER.info("Data to be sent:");
    LOGGER.info(new String(this.content, encoding));

    // 11.
    while(true) {
      try {

        Socket socket = server.accept(); // 12. 사용자가 f5눌러서 요청할때까지 block중, 요청오면 socket만들어짐
        HttpHandler handler = new HttpHandler(socket); // 13. 러너블객체 가동
        new Thread(handler).start();

      } catch(IOException ex) {
        LOGGER.log(Level.WARNING, "Exception accepting connection", ex);
      }catch(RuntimeException ex) {
        LOGGER.log(Level.SEVERE, "Unexpected error", ex);
      }
    }
    }catch(IOException ex) {
      LOGGER.log(Level.SEVERE, "Could not start server", ex);
    }
  } // start()

  // 14. 쓰레드 이용하기위해 Runnabled을 구현한 내부클래스 정의
  // http의 사용자가 요청 처리 부분이 별도의 스레드로 실행됨
  // 채팅프로그램) 메인: 무한루프 & send, receive: 스레드로 실행  --> 이 방식과 비슷
  private class HttpHandler implements Runnable {
    private final Socket socket;

    public HttpHandler(Socket socket) {
    this.socket = socket;
    }

    @Override
    public void run() {
      try {

        // 15. 파일 출력 = Response (양식: Status Line - Header - empty line - body)
        OutputStream out = new BufferedOutputStream(socket.getOutputStream());

        // 16. 사용자 요청 읽기
        BufferedReader br = new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()));

        // 16-1. BufferedReader로 사용자 요청을 한줄씩 읽는다.
        StringBuilder request = new StringBuilder();
        while (true) { // while문으로 request header 부분 만드려는 중
          String str = br.readLine();

          if (c == '\r' || c == '\n' || c== -1) break; // 아래 if문과 같음
          if (str.equals("")) break; // 16-2. 빈줄이 포함되었으면 while문 벗어남 -> empty line
          request.append(str + "\n"); // 16-3. 읽는 족족 request 헤더에 추가중
        }
        // 17. 브라우저(사용자)가 보낸 request 헤더 출력
        // GET / HTTP/1.1
        //     └─▶ /: 우리 서버는 localhost/ddd.aaaa 해도 무조건 test.html만 보내주게 되어있음 --> Run Configuration에서 설정해둠
        System.out.println("요청헤더:\n" + request.toString()); 

        // HTTP/1.0 이나 그 이후의 버전을 지원할 경우 MIME 헤더를 전송한다.
        if (request.toString().indexOf("HTTP/") != -1) {
          out.write(header);
        }

        System.out.println("응답헤더:\n" + new String(header));

        out.write(content);
        out.flush();

      } catch (IOException ex) {
        LOGGER.log(Level.WARNING, "Error writing to client", ex);
      } finally {
        /*try { // 여기 왜 주석한거지?
          socket.close(); // 소켓 닫기(연결 끊기)
        } catch (IOException e) {
          e.printStackTrace();
        }*/
      }
    } // run()
  } // HTTPHandler
  
  public static void main(String[] args) {
    // 1. 대기(listen)할 포트를 설정한다.
    int port;
    try {
      port = Integer.parseInt(args[1]); // 2. Run Config에서 설정한 매개변수 args 인자값 3개중 1번방 = port 번호
      if(port < 1 || port > 65535) port = 80; // 3. port 범위 밖에 있으면 default 80으로 설정
    } catch (RuntimeException ex) {
      port = 80; // 4. exception 발생 시에도 80으로 설정
    }
  
    /*
     * 원래 웹서버: 사용자가 원하는 리소스를 url로 지정하여 해당 리소스를 찾아서 보내줌
     * 우리의 심플 웹서버: 사용자가 접속만 하면 그냥 test.html 전송
     */
    FileInputStream fis = null;
    try {
      // 6. 파일 출력을 위해 파일 읽기
      //Path path = Paths.get(args[0]);
      //byte[] data = Files.readAllBytes(path);
      File file = new File(args[0]);
      byte[] data = new byte[(int) file.length()];
      fis = new FileInputStream(file);
      fis.read(data);

      // 7. 해당 파일이름을 이용하여 Content-type 정보 추출하기 -> Content Type = MIME Type = 파일 타입
      String contentType = URLConnection.getFileNameMap().getContentTypeFor(args[0]); // text/html
      System.out.println("contentType => " + contentType);

      // 8. 서버 객체 생성해서 start
      SimpleHTTPServer server = new SimpleHTTPServer(data, encoding, contentType, port);
      server.start();
    } catch (ArrayIndexOutOfBoundsException ex) {
      System.out.println("Usage: java SimpleHTTPServer filename port encoding");
    } catch (IOException ex) {
      LOGGER.severe("IO Error: " + ex.getMessage());
    } finally {
      try {
        fis.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  } // main()
} // class
profile
갈 길이 멀다

0개의 댓글