본문 바로가기

전공 과목 시험정리/C 프로그래밍

웹에서 서버정보 파싱

전에 올렸었던

http://blog.kim82536.pe.kr/entry/%EC%9B%B9-%EC%9D%91%EB%8B%B5%EC%97%90%EC%84%9C-%EC%84%9C%EB%B2%84-%EC%A0%95%EB%B3%B4-%EC%B6%94%EC%B6%9C


의 프로그램을 약간 수정해 보았다.



원래 소스

project.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "header/create_request.h"
#include "header/dns.h"
#include "header/parsing.h"
#define BUFF_SIZE 400
 
int main(int argc,char **argv){
    char request[100]="\0"// To make request
//    char URL[]="www.naver.com";
    char *URLaddr;
 
    int s,n;
    char *haddr;
    struct sockaddr_in server_addr;
    // socket address struct
    
    //인자값 체크
    if(argc<2){
        printf("Usage :\n%s [URL]\n",argv[0]);
        return 1;
    }
 
 
 
//    char buf[BUFF_SIZE+1];
    char *buf;
    buf=(char *)malloc(BUFF_SIZE+1);
    // make buffer + NULL size
 
    //dns()함수에서 리턴 문자열을 받기 위한 동적 메모리 할당
    URLaddr=(char *)malloc(20);
 
 
    //IP주소 담아오기
    URLaddr=dns(argv[1]);
    if(!strcmp(URLaddr,"error")){
        printf("dns() error!!!\n");
        return 1;
    }
 
    //요청문자 담아오기
    make_request(argv[1],request);
 
    s=socket(AF_INET,SOCK_STREAM,0);
    if(s < 0){
        printf("SOCK ERROR!\n");
        return 1;
    }
    
    bzero((char *)&server_addr,sizeof(server_addr));
 
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(URLaddr);
    server_addr.sin_port = htons(80);
 
    if(-1 == connect(s,(struct sockaddr *)&server_addr,sizeof(server_addr))){
        printf("Connect() Failed..\n");
1;
    }
    
    memset(buf,'\0',BUFF_SIZE+1);
 
    // 정보 출력
    printf("Request : \n%s\n\n",request);
    printf("Target URL : %s\nTarget IP : %s\n",argv[1],URLaddr);
    
    send(s,request,strlen(request)+1,0);
    if(0==read(s,buf,BUFF_SIZE)){
        printf("read() Failed...\n");
        return 1;
    }
    //printf("Recv : \n%s\n",buf);
 
    //Recv 에서 서버 정보만 자르기
    char *result;
    result=(char *)malloc(strlen(buf));
    result=buf;
    result=strfin(result,"<!doctype");
    printf("\n\n\n------------------Result-------------------\n");
    printf("%s\n",result);
 
 
 
    
    close(s);
    return 0;
}
 
/* Recv : 
 * Target URL : www.naver.com
 * Target IP : 125.209.222.142
 * Recv : 
 * HTTP/1.1 200 OK
 * Server: nginx
 * Date: Sun, 04 Jan 2015 04:54:16 GMT
 * Content-Type: text/html; charset=UTF-8
 * Transfer-Encoding: chunked
 * Connection: close
 * Cache-Control: no-cache, no-store, must-revalidate
 * Pragma: no-cache
 * P3P: CP="CAO DSP CURa ADMa TAIa PSAa OUR LAW STP PHY ONL UNI PUR FIN COM NAV INT DEM STA PRE"
 * X-Frame-Options: SAMEORIGIN
 */
 
cs




create_request.h

1
2
3
4
5
6
#include <string.h>
 
void make_request(char host[],char recv[]){
    sprintf(recv,"GET / HTTP/1.1\nHost: %s\nUser-Agent: Mozilla4.0\nConnection: close\r\n\n",host);
}
 
cs


dns.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
char *dns(char host[]){
    struct hostent *host_entry;
    int ndx;
    char *back;
    back=(char *)malloc(20);
    host_entry = gethostbyname(host);
    if(!host_entry){
        printf("gethostbyname() failed...\n");
        return "error";
    }
    
    strcpy(back,inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[0]));
    return back;
}
cs



erase_data.h

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
 
char *erase_data(char data[],char start[]){
    int i;
    for(i=0;i<strlen(start);i++)
        data++;
    return data;
}
 
cs


parsing.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "strfin.h"
#include "erase_data.h"
 
/*
char *strsta(char data[],char sta[]){
    char *data_copy;
    char *sta_copy;
    int i;
    data_copy=data;
    sta_copy=sta;
    printf("before_data_copy = %s\n",data_copy);
    for(i=0;i<strlen(data);i++){
        if(!strcmp(data_copy,sta_copy)){ //same
            for(i=0;i<strlen(sta_copy);i++)    
                data_copy++;
            printf("data_copy = %s\nsta_copy = %s\n",data_copy,sta_copy);
            printf("Same!\n");
        }
        else{    
            data_copy++;
            printf("data_copy = %s\nsta_copy = %s\n",data_copy,sta_copy);
            printf("NoNo!\n");
        }
    }
    printf("data_copy = %s\n",data_copy);
    return data_copy;
}
*/
 
 
 
char *parsing(char data[],char start[],char fin[]){
    //printf("\n\n----------------parsing.h------------\n");
    //printf("data = \n%s\n\n\n\n",data);
    //printf("start = \n%s\n\n\n\n",start);
    //printf("fin = \n%s\n\n\n\n",fin);
 
    data=strstr(data,start);
    //printf("strstr()_after_data = \n%s\n\n\n\n\n",data);
    //is_testing_string
    
    data=erase_data(data,start);
    //printf("strstr()_erase_data()_after_data = \n%s\n\n\n\n\n",data);
    //_testing_string
 
    data=strfin(data,fin);
    //printf("strstr()_erase_data()_strfin()_after_data = \n%s\n\n\n\n\n",data);
    //_testing
    
    return data;
 
}
cs


strfin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
 
char *strfin(char data[],char fin[]){
    char *data_copy;
    int i;
    char *trash='\0';
 
    data_copy=data;
 
    for(i=0;i<strlen(data);i++){
        if(!strncmp(data_copy,fin,strlen(fin))){
            *data_copy='\0';
        }
        else{
            data_copy++;
        }
        
    }
    
    return data;
}
cs



기존 소스는 일단

1. 헤더 파일의 무분별한 사용. --> 소스의 가독성이 떨어짐

2. GET 의 사용으로 헤더만 추출하는 연산과정이 너무 복잡함.

3. 중복 #include 가 너무 심함.


이러한 문제점을 해결하기 위해

일단 HEAD 를 사용해 서버의 정보만 오도록 하고, 헤더파일의 사용은 최소한으로 줄임.

그리고 Standard Input Output 이 필요할 경우에만 stdio.h 를 포함시킴.

그리고 함수에 대한 필요 헤더파일만 선언해두고, 나머지 필요 없는 헤더 포함은 없앰.



+++) 함수를 만들고, 인자값과 반환값을 교환할 때 주의할 점.


strfin을 만들 때, 무작정 문자열을 넣고 수정하고 난 후에 반환을 하게 했는데,

큰 오산이었다.

1
strfin("test");
cs

 이렇게 호출하게 되면, 

저 test 라는 문자열은 const 형이 되어버려서 수정이 되질 않는다.

따라서 호출시에 

1
2
char buffer[]="test";
strfin(buffer);
cs

이렇게 호출을 하거나,

아니면 받는 쪽에서 변수를 하나 만들어 문자열을 담은 후에 연산하고 반환해야 한다.


저 프로그램을 만들때는 그냥 malloc 으로 힙 메모리에 박아 넣었었는데, 호출 시에 buffer 에 담아서 const 형이 되지 않게 넘겨주는 방법은 나중에 안 방법이다.


이 const 형 때문에 Segmentation fault 가 났었다.

(애꿎은 널바이트 넣는 루틴만 계속 수정하고..)




------- 호스트 정보 추출 -------
2014.05 기준으로 gethostbyname() 함수는 구식방법으로, 표준이 아니라고 한다.

해당 함수 대신 getnameinfo() 함수를 사용하려 한다.


- getnameinfo() 함수에 대한 정보가 거의 영문이라 애 먹고 있다.
 추후에 단어사전 꺼내서 하나하나 번역해가면서 소스를 봐야 할듯.

해당 addr 에 대한 구조체를 선언후에 return 값을 받는 함수인 것 같다.



--------------------------

좀더 쉬운 소켓 연결과 간결한 소스 작성을 위해 파이썬으로 제작하기로 했다.



+) 파이썬은 도메인으로도 소켓 연결이 된다. 확실히 소켓 프로그래밍이 편한 언어인 것 같다




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*- coding:utf-8 -*-
import sys 
from socket import *
 
 
# argc check
if len(sys.argv)<3:
        print "Usage : %s [URL] [PORT]" %(sys.argv[0])
        exit()
 
request="HEAD / HTTP/1.1\nHost:" + sys.argv[1+ "\n\n"
 
target=(sys.argv[1],int(sys.argv[2]))
 
s=socket(AF_INET,SOCK_STREAM)
s.connect(target)
 
s.send(request)
 
get=s.recv(1024)
 
print get.split("\r\n")[1]
cs


argv 로 URL 과 PORT 를 받고, HEAD 리퀘스트를 날려서 받은 응답에서 서버 정보만 추출해서 보여준다.


파이썬 자체에서는 argc 가 없어 len(sys.argv) 를 이용했다.



+) 서버에 따라 Server 헤더가 순서가 바뀌는 것을 확인했다

따라서 print get.split("\r\n")[1] 처럼 서버 정보를 추출 하는 것은 약간 위험하다고 보고, split 으로 나눈 뒤에 "Server" 라는 문자열을 비교하는 방식으로 변경해야 할 것 같다.


C언어에서는 도메인으로 소켓 연결시 연결이 되지 않았는데, 파이썬에서는 기본적으로 연결이 된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sys
from socket import *
 
 
# argc check
if len(sys.argv)<3:
    print "Usage : %s [URL] [PORT]" %(sys.argv[0])
    exit()
 
request="HEAD / HTTP/1.1\nHost:" + sys.argv[1+ "\n\n"
 
target=(sys.argv[1],int(sys.argv[2]))
 
s=socket(AF_INET,SOCK_STREAM)
s.connect(target)
 
s.send(request)
 
get=s.recv(1024).split("\r\n")
 
 
i=0
 
while(1):
    try:
        tmp=get[i]
 
    except IndexError:
        break
    
    if tmp[:7]=="Server:":
        print tmp    
    
    i=i+1
 
cs




$ python main.py localhost 80

Server: apache

$


성공적으로 나오는 것을 볼 수 있다.


이제 응답이 올 때, 헤더의 순서가 아닌 Server: 뒤에 붙는 문자열을 파싱하므로, 다른 서버에게 요청해도 서버의 정보만 정확하게 받아올 수 있다.





----- 마치며 -----

원래 저 프로그램은 처음에 방학을 한 후에, 나태해 질까봐 매일 2 시간씩 짬짬이 만들던 프로그램이었다. 하지만 저때 소켓 소스도 많은 블로그를 봐가며 코드도 가져와서 써보고 여러 오류도 고쳐가며 만든 프로그램 이었다. 

확실히 시간이 얼마 지나고 파이썬으로 같은 프로그램을 작성해 보니까 한결 더 간결하고 간단한 코드로 짤 수 있었다. 

그동안 놀지만은 않은 것 같아 다행이라 생각했다.