네트워크 프로그래밍을 공부하면서 배운 개념이 2가지가 있었다. 바로 소켓과 HTTP이다.
개요 : 네트워크를 공부하면서 엄청 자주 들었었는데 "HTTP로 데이터를 주고 받는다" 또는 "소켓으로 연결하여 통신한다"
정도의 얕은 이해로만 알고 있었다. 하지만 이번에 직접 소켓과 HTTP로 통신하고 직접 request, response를
구현해보면서 그 개념을 깊에 이해할 수 있었다.
나의 이해를 한 번 공유해보려고 한다.
배경 : 쉬운 네트워크의 이해, 네트워크 == 우편?
내가 이해한 네트워크는 아주 예전에 사용하던 통신 방법인 우편과 똑같다.
1. A가 B에게 편지을 보내려면 A와 B는 각자의 우체통이 필요하다.
2. 각자의 우체통이 있다고 하면 A는 A의 우체통에 보낼 편지를 넣으면 주소에 따라 우체부가 B의 우체통에 보내진다.
3. B는 자신의 우체통에서 편지를 꺼내서 읽고 A에게 답장 편지를 작성해 B의 우체통에 다시 넣어서 A에게 보낸다.
여기서 네트워크 개념에서 바라보면
소켓
우체통이 바로 소켓이다. (각자의 소켓을 통해 연결하여 통신하는 것!)
A와 B의 주소는 IP, A,B 사람 이름은 Port번호 이다.
(주소는 알지만 누구에게 보내져야 할지를 모르면 안되기 때문에 Port 또한 필요하다.)
HTTP
편지는 데이터이고 이 편지 내용의 규칙과 읽는 방법이 바로 HTTP이다. (통신의 규약!)
좀 더 세분과 해보면 보낼 편지는 Request이고 답장 편지가 Response이다.
네트워킹
마지막으로 우체부는 라우터와과 같은 네트워크 장비이다.
추가적으로 배송 방식이 TCP, UDP와 같은 전송 제어 프로토콜이다.
( 예를 들어 빠른 로켓배송은 UDP, 속도는 느리지만 안전한 일반 배송은 TCP이다. )
예시를 네트워크로 다시 정리해보면 이제 네트워크의 개념을 쉽게 이해할 수 있다.
1. ClientA가 Client B에게 Data을 보내려면 ClientA와 ClientB는 각자의 Socket이 필요하다.
2. 각자의 Socket이 있다고 하면 ClientA는 Client A의 Socket에 Request를 넣으면 주소에 따라 네트워크 통신장비가 ClientB의 Socket에 보내진다.
3. ClientB는 자신의 Socket에서 Data를 꺼내서 읽고 ClientA에게 Response를 작성해 ClientB의 Socket에 다시 넣어서 ClientA에게 보낸다.
그렇다면 프로그래밍에서는 어떻게 소켓과 HTTP를 사용할까?
특정 URL과 통신!
"www.example.com" 페이지와 데이터를 구조 받는 과정을 코드로 보이면 다음 코드 예시와 같다.
다음 코드는 request를 보내는 과정이다.
try (
Socket client = new Socket(url, 80);
BufferedReader clientIn = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter clientOut = new PrintWriter(client.getOutputStream(),false);
) {
// making request
StringBuilder request = new StringBuilder();
StringBuffer requestHeader = new StringBuffer();
StringBuffer requestBody = new StringBuffer();
String requestLine = method+" "+path+" "+"HTTP/1.1";
String requestHost = "Host:"+" "+host;
String requestUserAgent = "User-Agent:"+" "+"curl/7.79.1";
String requestAccept = "Accept:"+" "+"*/*";
String requestAddition = line;
request.append(requestHeader.toString());
request.append(requestBody.toString());
//System.out.println(request.toString());
clientOut.write(request.toString());
clientOut.flush();
} catch (IOException e) {
System.out.println("error!!!");
}
다음 코드는 response를 받는 과정이다.
try (
Socket client = new Socket(url, 80);
BufferedReader clientIn = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter clientOut = new PrintWriter(client.getOutputStream(),false);
) {
// making response
StringBuffer response = new StringBuffer();
StringBuffer responseHeader = new StringBuffer();
StringBuffer responseBody = new StringBuffer();
String message;
while((message = clientIn.readLine())!=null){
responseHeader.append(message+"\r\n");
}
} catch (IOException e) {
System.out.println("error!!!");
}
하지만 HTTP라이브러리가 이미 구현되어 있어 우리는 직접 Request와 Response를 만들 필요 없이 쉽게 사용가능하다!
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(String.format("http://example.com:%d/some/path?query=123", PORT_NUMBER)))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// request에서 얻을 수 있는 값들
URI requestUri = request.uri();
String requestMethod = request.method();
Optional<HttpRequest.BodyPublisher> requestBody = request.bodyPublisher();
HttpHeaders requestHeaders = request.headers();
// 요청의 URI 정보 추출
String requestHost = requestUri.getHost();
String requestPath = requestUri.getPath();
String requestQuery = requestUri.getQuery();
String requestScheme = requestUri.getScheme();
int requestPort = requestUri.getPort();
// 요청 헤더에서 특정 값 가져오기
List<String> requestContentType = requestHeaders.allValues("Content-Type");
List<String> requestUserAgent = requestHeaders.allValues("User-Agent");
// response에서 얻을 수 있는 값들
int responseStatusCode = response.statusCode();
String responseBody = response.body();
HttpHeaders responseHeaders = response.headers();
Optional<HttpRequest> responseRequest = response.request();
Optional<HttpClient.Version> responseVersion = response.version();
// 응답 본문(body)에서 얻을 수 있는 값
int responseBodyLength = responseBody.length(); // 응답 본문의 길이
boolean isResponseEmpty = responseBody.isEmpty(); // 응답이 비어있는지 여부
boolean containsJson = responseBody.contains("{") && responseBody.contains("}"); // JSON 데이터 여부 확인
// 응답 헤더에서 특정 값 가져오기
List<String> responseContentType = responseHeaders.allValues("Content-Type"); // 콘텐츠 타입 (예: text/html, application/json)
List<String> responseContentEncoding = responseHeaders.allValues("Content-Encoding"); // 인코딩 타입 (예: gzip, br)
List<String> responseSetCookie = responseHeaders.allValues("Set-Cookie"); // 쿠키 정보
List<String> responseServer = responseHeaders.allValues("Server"); // 서버 정보 (예: Apache, Nginx)
List<String> responseCacheControl = responseHeaders.allValues("Cache-Control"); // 캐시 관련 설정
List<String> responseDate = responseHeaders.allValues("Date"); // 응답 날짜
처음 네트워크를 공부할 때 개념으로서만 이해하고 있던 소켓과 HTTP에 대해서 깊이 있게 이해해보며 어떻게 데이터를 주고 받고 어떻게 데이터를 요청하고 보내는지를 이해할 수 있었다.
직접 Request와 Response를 만들어보고 실제로 데이터도 읽어보니 신기하기도 하고 네트워크에 대한 이해력이 높아진거 같아 뿌듯한 시간이었다.