Xerath(제라스) 2022. 5. 13. 02:26
728x90
반응형

- HTTP 프로토콜이란?

: 웹서핑을 할 때 많이 볼 수 있음. TCP 기반으로 구동되는 text-based client-server 프로토콜임.

-> ∴ HTTP 서버가 구동될 때 일반적으로 80 port를 사용함.(그러다보니 웹서버로 접근 시 80 port로 접근 많이 함.)

 

- HTTP는 보안이 결여되어 있음

: HTTP 트래픽을 분석해보면 암호화되지 않은 상황에서 data가 오감.

∴ 스니퍼 같은 도청 공격자에게 정보가 쉽게 노출됨.

 

-> 기본적인 정보 보호 기술과 Transport Layer Security(TLS)를 배울 것임.

+ HTTP가 TLS 위에 구현된 HTTPS도 구현할 것임.

 

- HTTP 프로토콜의 구성

1) HTTP Request

1. GET : client가 어떤 resource를 server로부터 가져오고 싶을 때

2. HEAD : client가 어떤 resource의 추상적인 정보(ex) 파일의 크기)를 server로부터 가져오고 싶을 때

3. POST : client에서 server에 정보를 보낼 때 

4. PUT : 새로운 데이터를 저장하거나 기존 데이터를 업데이트를 할 때 사용함. POST와 매우 비슷하지만 명확한 저장 위치를 명시하여 업데이트를 함. 

5. DELETE : server에 있는 data를 삭제할 때 사용함. URI form으로 삭제하려는 resource를 지정해줘야 함.

 

2) HTTP Response

1) 200(OK) : request가 성공적으로 수행된 경우. 수행된 후의 resource 값을 알려줌.

2) 301(Moved Permanently): resource가 옮겨졌을 때. 해당 위치의 header field를 알려줌.

3) 400(Bad Request) : server가 client Request를 이해X or 지원 불가능할 때

4) 401(Unauthorized) : client가 resource를 받을 수 있는 권한이 없을 때

5) 403(Forbidden) : client의 접근이 금지되었을 때

6) 404(Page Not Found or File Not Found) : 페이지가 없을 때

7) 500(Internal Server Error) : server가 client Request 처리하다가 에러났을 때

 

- URL(Uniform Resource Locators)이란?

: Resource의 위치를 명시함.

- Web Client란?

: HTTP request를 서버에 보내고 HTTP response를 받아와서 출력해주는 client로

입력받은 URL을 통해 host와 연결 및 자원을 검색함.

 

함수 3가지

1) parse_url() : URL을 파싱하는 함수로, 입력받은 URL로부터 hostname, port 번호, document path를 받아옴.

-> 이거 설명 안할거라고 함.

2) send_request() : char형 버퍼를 정의하고 이곳에 HTTP request를 담아서 패킷을 만들어 전송함.

void send_request(SOCKET s, char *hostname, char *port, char *path) { 
    char buffer[2048]; //buffer에 위의 매개인자들을 HTTP 형식대로 넣어줌.

    sprintf(buffer, "GET /%s HTTP/1.1\r\n", path); //GET 메소드를 쓰고 HTTP1.1버젼을 쓴다.
    sprintf(buffer + strlen(buffer), "Host: %s:%s\r\n", hostname, port); 
    sprintf(buffer + strlen(buffer), "Connection: close\r\n"); 
    sprintf(buffer + strlen(buffer), "User-Agent: honpwc web_get 1.0\r\n"); 
    sprintf(buffer + strlen(buffer), "\r\n");
    
    send(s, buffer, strlen(buffer), 0); //버퍼에 저장된 내용을 소켓s를 통해 보냄.
    printf("Sent Headers:\n%s", buffer); //어떤 데이터를 보냈는지 확인.
}

3) connect_to_host() : 설정한 URL로 접근하여 hostname과 port 번호로 서버와 커넥션을 맺음.

SOCKET connect_to_host(char *hostname, char *port) { 
	printf("Configuring remote address...\n"); 
    struct addrinfo hints;
	memset(&hints, 0, sizeof(hints)); 
    hints.ai_socktype = SOCK_STREAM;
	struct addrinfo *peer_address;
	if (getaddrinfo(hostname, port, &hints, &peer_address)) { //hostname이랑 port번호로 IP주소 받아옴.
		fprintf(stderr, "getaddrinfo() failed. (%d)\n", GETSOCKETERRNO());
		exit(1); 
    }
    
	printf("Remote address is: ");
	char address_buffer[100];
	char service_buffer[100];
	getnameinfo(peer_address->ai_addr, peer_address->ai_addrlen,
			address_buffer, 
            sizeof(address_buffer), 
            service_buffer, 
            sizeof(service_buffer), 
            NI_NUMERICHOST);
	printf("%s %s\n", address_buffer, service_buffer); //추가적인 정보를 출력함.
    printf("Creating socket...\n");
    SOCKET server;
    server = socket(peer_address->ai_family, //소켓을 생성.
    		peer_address->ai_socktype, peer_address->ai_protocol); 
    if (!ISVALIDSOCKET(server)) {
    	fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
    	exit(1); 
    }
    printf("Connecting...\n");
    if (connect(server, peer_address->ai_addr, peer_address->ai_addrlen)) { //connect함.
    	fprintf(stderr, "connect() failed. (%d)\n", GETSOCKETERRNO());
    	exit(1); 
    }
    freeaddrinfo(peer_address);
    
    printf("Connected.\n\n");
    
    return server;
}

main 함수

int main(int argc, char *argv[]) { //패킷을 보내고 서버로부터 response를 받아옴.
    if (argc < 2) {
    	fprintf(stderr, "usage: web_get url\n"); return 1;
    }
    char *url = argv[1]; //입력해둔 url을 가져오고
    
    char *hostname, *port, *path;
    parse_url(url, &hostname, &port, &path); //url을 parsing해서 hostname,port,path를 입력받고
    
    SOCKET server = connect_to_host(hostname, port); //hostname, port번호로 server 소켓 생성.
    send_request(server, hostname, port, path); //server에 HTTP GET request를 전송함
    
    const clock_t start_time = clock(); //시간 측정하는 부분.
    
#define RESPONSE_SIZE 32768
    char response[RESPONSE_SIZE]; //response 배열을 만들고
    char *p = response, *q; //response 배열의 처음을 가리키는 p, 구현상 도움을 줄 q
    char *end = response + RESPONSE_SIZE; //response 배열의 끝을 가리키는 end
    char *body = 0; //받아온 response는 header와 body로 이뤄져 있을 텐데 그 중 body를 가리키는 포인터.
    
    enum {length, chunked, connection};
    int encoding = 0;
    int remaining = 0;
    
    while(1) {
    	if ((clock() - start_time) / CLOCKS_PER_SEC > TIMEOUT) { //시간이 5초 넘어가면 서버 에러로 처리.
			fprintf(stderr, "timeout after %.2f seconds\n", TIMEOUT); //#define TIMEOUT 5.0 
            return 1;
        }
        if (p == end) { //p가 끝에 도달했을 때 종료
           fprintf(stderr, "out of buffer space\n");
           return 1;
		}
		
        fd_set reads; 
        FD_ZERO(&reads); 
        FD_SET(server, &reads);
        
		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 200000;
        
        if (select(server+1, &reads, 0, 0, &timeout) < 0) { //select를 통해 패킷을 받을 준비를 함.
        	fprintf(stderr, "select() failed. (%d)\n", GETSOCKETERRNO());
            return 1;
		}

		if (FD_ISSET(server, &reads)) { //server에 관심있는 읽을거리가 있으면
        	int bytes_received = recv(server, p, end - p, 0); //recv로 서버 소켓의 data 읽어옴. p에 읽어오는데 end-p만큼 읽어옴. end-p가 response 길이잖아!ㅎㅎ
        	if (bytes_received < 1) { //1보다 작다 = 서버로부터 close 요청이 온 것임.
				if (encoding == connection && body) { //encoding==1.length 2.chunked 3.close(connection), body가 있으면 body를 출력함.
           			printf("%.*s", (int)(end - body), body);
				}
				printf("\nConnection closed by peer.\n");
				break; 
            }
            
			p += bytes_received; //읽은만큼 p를 증가시킴
            *p = 0; //string 관련 함수를 쓸 수 있게끔 0을 넣어줌.
			
            //body를 처리하는 부분.
            if (!body && (body = strstr(response, "\r\n\r\n"))) { //response에서 \r\n\r\n로시작하는 문자열 검색 후 시작점을 포인터로 반환.
            //이때 \r\n\r\n은 header와 body를 나누는 경계임. 즉, response = header + \r\n\r\n + body
                *body = 0; //나중에 header 출력하려고 끝부분을 null로 표시해두는 것임.
                body += 4; //\r\n\r\n을 건너뛰려고 +4 하는 것.
            	
                printf("Received Headers:\n%s\n", response);
                
                q = strstr(response, "\nContent-Length: "); //response 패킷의 총 길이를 q에 가져옴.
				if (q) {
                    encoding = length;
                    q = strchr(q, ' '); q에 ' '이 있는지 검사. 
                    q += 1; //length 숫자의 맨 앞을 가리킴
                    remaining = strtol(q, 0, 10); //10진수로 읽어오고 body의 길이(즉,Content-Remaining값)가 remaining에 저장됨. 저장할 포인터는 관계없으므로 0을 씀.
                
                } else {
                    q = strstr(response, "\nTransfer-Encoding: chunked"); //response에 "\n~~~"가 존재하는지 확인하는 함수.
                    if (q) { //chunked여부 확인.
                        encoding = chunked;
                    	remaining = 0; //받으면서 길이를 검사해야 하므로 계산할 수 없어서 0으로 세팅.
                    } else { //이것도 아니면 connection(close)로 처리.
                        encoding = connection;
                    }
                }
                printf("\nReceived Body:\n");
            }
            
            if (body) {
			if (encoding == length) {
				if (p - body >= remaining) { //p-body가 body길이보다 크거나 같다 = 그만가! 멈춰~!!!
                	printf("%.*s", remaining, body); 
                	break;
                    }
			} else if (encoding == chunked) {
        		do {
                    if (remaining == 0) {
                        if ((q = strstr(body, "\r\n"))) {
                    		remaining = strtol(body, 0, 16); 
                            if (!remaining) goto finish; 
                            body = q + 2; // \r\n고려
                        } else {
                            break;
                    	} 
                    }
                    if (remaining && p - body >= remaining) { 
                    	printf("%.*s", remaining, body);
                        body += remaining +2; //\r\n 고려
                        remaining = 0;
                        }
                    } while (!remaining);
                }
			} //if (body) 
		} //if FDSET
    } //end while(1)
finish:

- HTTP POST request

: data를 client에서 server로 보냄.(web client에서 생성한 data를 web server로 전송하는 역할)

- 포맷은 Content-Type Header에 의해 정의되고 매우 다양함.

- 웹사이트에 form 형태(ex)로그인 폼)가 있을 때 POST를 많이 씀.

 

 

 

728x90
반응형