관리 메뉴

사과하는 제라스

13강. HTTP and Web Client 본문

대학 전공 공부/네트워크 프로그래밍

13강. HTTP and Web Client

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
    반응형

    '대학 전공 공부 > 네트워크 프로그래밍' 카테고리의 다른 글

    16. Basic OpenSSL  (0) 2022.06.08
    15. 네트워크 보안  (0) 2022.06.08
    14강. 보안 기본 개념  (0) 2022.05.21
    12강. DNS  (0) 2022.05.13
    11강. Error, IPv6 and Packet Socket  (0) 2022.05.12