Connect → Request → Response → Close
요청 메시지 | 응답메시지 |
---|---|
Request Line | Status 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: 상태 |
Header | Header |
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 Line | Empty Line |
Body | Body |
- 보내거나 받고자 하는 실제 데이터를 의미 - 포스트방식에만 존재 (GET등의 경우에는 요청시 생략 가능) | <html> <head> <title>An Example Page</title> </head> <body> Hello, World! </body> </html> |
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