2020/04/20 - [개발자/Python] - Pycharm (파이참) 소켓 서버 띄우기 에서 이어짐...

 

1. 새 Request 생성하기

New -> Request

 

2. URL 붙여넣기

 

자동으로 Key, Value를 인덱싱한다.

GET 방식의 URL에서 ? 뒤에는 쿼리다. 그리고 각각의 쿼리는 &로 구분된다. =

?Key1=Value1&Key2=Value2&Key3=Value3

더보기

GET 방식은 직관적이나 URL에 Value가 그대로 드러나기 때문에 아이디나 비밀번호같은 민감한 정보는 포함할 수 없다.

그런 경우 POST 방식으로 암호화 해서 보낸다.

 

 

3. URL 보내기 (GET 방식)

파이썬에서 소켓 서버를 실행하고 해당 URL을 Postman을 통해서 보내본다.

'localhost:8090/api/v1/getrecord/1?k=v'로 보낸 결과.

 

 

'localhost:8090/api/v1/getrecord/3?k=v'로 보낸 결과. 400 Bad Request

 

그리고 소스코드가 GET, POST 방식만 처리하는 코드이기 때문에 PUT 방식은 처리를 하지 못 한다.

 

GET 방식 코드

    def do_GET(self):    # Override다. Super class인 BaseHTTPRequestHandler에 'do_GET' 함수가 있는데 Sub class인 myHandler에서 재정의해서 사용한다.
                         # 자바에서는 @Override를 붙여서 명시해줬지만 파이썬에서는 명시할 필요 없이 그냥 Super class에 있는 함수명을 그대로 가져다 다시 정의하면 된다.

        print('Get request received')
        if None != re.search('/api/v1/getrecord/*', self.path):

            recordID = (self.path.split('/')[-1]).split('?')[0]
            print("recordID = ", recordID)
            if recordID == "1" :
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                # Send the html message
                self.wfile.write(bytes("<html><head><title>Title goes here.</title></head>", "utf-8")) #"euc-kr"
                self.wfile.write(bytes("<body><p>This is a test.</p>", "utf-8"))
                self.wfile.write(bytes("<p>Your accessed path: %s</p>" % self.path, "utf-8"))    # %s% -> self.path가 요청한 경로 URL을 그대로 받을 수 있다.
                self.wfile.write(bytes("</body></html>", "utf-8"))

            else:
                self.send_response(400, 'Bad Request: record does not exist')
                self.send_header('Content-Type', 'application/json')
                self.end_headers()

 

 

4. URL 보내기 (POST 방식)

def do_POST(self):
if None != re.search('/api/v1/addrecord/*', self.path):

소스코드를 보면 POST방식은 '/api/v1/addrecord/*'로 보내야 한다는 것을 알 수 있다.

그리고 POST 방식은 Body에 정보를 실어 보낸다.

Body에서 raw를 클릭하고 {"1":"2"}를 입력하고

Headers에도 Key와 Value를 넣고 보내보자.

(Value는 ; 으로 구분)

 URL은 'localhost:8090/api/v1/addrecord/1'

 

디버깅 시작

 if ctype == 'application/json':
                content_length = int(self.headers['Content-Length']) # 48 bytes
                post_data = self.rfile.read(content_length)    # .rfile.read( ) 읽어와라. (byte로 읽어옴.)
                receivedData = post_data.decode('utf-8')    # .decode('utf-8')을 통해 우리가 아는 문자열로 바뀜.
                print(type(receivedData))
                tempDict = json.loads(receivedData) #  load your str into a dict    # 위에 문자열로 바꾼걸 다시 딕셔너리로 바꿈.
                #print(type(tempDict)) #print(tempDict['this'])
                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(tempDict), "utf-8"))

이 부분을 보면, ctype = application/json일 때 작동하고

외부에서 들어오는 데이터는

.rfile.read( )를 통해 byte로 읽어온다 -> .decode('utf-8')을 통해 문자열로 바뀐다. -> json.loads( ) 를 통해 딕셔너리로 바뀐다.

외부로 나가는 데이터는 역순으로 진행된다.

json.dumps(    )를 통해 딕셔너리를 문자열로 변환 -> bytes(    )를 통해 문자열을 byte로 바꾼다 -> self.wfile.write(    )를 통해 내보낸다.

 

            elif ctype == 'application/x-www-form-urlencoded':
                content_length = int(self.headers['content-length'])
                # trouble shooting, below code ref : https://github.com/aws/chalice/issues/355
                postvars = parse_qs((self.rfile.read(content_length)).decode('utf-8'),keep_blank_values=True)

                #print(postvars)    #print(type(postvars)) #print(postvars.keys())

                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(postvars) ,"utf-8"))

이 부분을 보면, ctype = application/x-www-form-urlencoded일 때 작동하고

content_length = int(self.headers['content-length']) 이 부분을 보면

int(self.headers['content-length']) -> 자기 자신의 헤더에서 Key가 'content-length'의 값을 찾아서 정수로 형변환한다.

self.headers를 열어보면 다음과 같은 값들이 담겨있다.

Content-Type: application/x-www-form-urlencoded
User-Agent: PostmanRuntime/7.24.1
Accept: */*
Postman-Token: a46ad098-08de-4169-a57f-739801d728d7
Host: localhost:8090
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 9

postvars = parse_qs((self.rfile.read(content_length)).decode('utf-8'),keep_blank_values=True) 이 부분을 보면 

parse_qs((self.rfile.read(content_length)).decode('utf-8'),keep_blank_values=True)
->  자기 자신을 파일로 읽어라(어떤 정보? content_length) , 그리고 utf-8로 디코딩해라 , 그것을 List로 묶어 Dic으로 반환하라.(옵션 : keep_black_values=True)

더보기

parse_qs : Value를 List로 묶어 Dictionary로 반환.

parse_qsl : Key:Value 쌍을 각각 Tuple로 만들어 List로 반환.

 

 

POST 방식 코드

    def do_POST(self):
        if None != re.search('/api/v1/addrecord/*', self.path):
            ctype, pdict = cgi.parse_header(self.headers['content-type']) # application/json;encoding=utf-8;lang=ko;loc=seoul;...
            print(ctype) # application/json
            print(pdict) # {encoding:utf-8, lang:ko, loc:seoul}

            if ctype == 'application/json':
                content_length = int(self.headers['Content-Length']) # 48 bytes
                post_data = self.rfile.read(content_length)    # .rfile.read( ) 읽어와라. (bytes로 읽어옴.)
                receivedData = post_data.decode('utf-8')    # .decode('utf-8')을 통해 우리가 아는 문자열로 바뀜.
                print(type(receivedData))
                tempDict = json.loads(receivedData) #  load your str into a dict    # 위에 문자열로 바꾼걸 다시 딕셔너리로 바꿈.
                #print(type(tempDict)) #print(tempDict['this'])
                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(tempDict), "utf-8"))    # 위에서 bytes -> str -> dic 으로 바꿔서 읽어들였던 것을 역순으로 해서 내보낸다. dic -> str -> bytes

            elif ctype == 'application/x-www-form-urlencoded':
                content_length = int(self.headers['content-length'])
                # trouble shooting, below code ref : https://github.com/aws/chalice/issues/355
                postvars = parse_qs((self.rfile.read(content_length)).decode('utf-8'),keep_blank_values=True)

                #print(postvars)    #print(type(postvars)) #print(postvars.keys())

                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(postvars) ,"utf-8"))
            else:
                self.send_response(403)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()

        else:
            self.send_response(404)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()

 

 

 

전체 코드

#!/usr/bin/python
# -*- coding: utf-8 -*-
# from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer

# 필요한 모듈을 불러옴.
from http.server import BaseHTTPRequestHandler,HTTPServer    #소켓 서버 실행용
from socketserver import ThreadingMixIn    # 아직 안 씀. 근데 왜 음영처리 되어있지?
import json    # requests : response를 위해 json 형식을 사용함. Key:Value 쌍.
import re      # Regular Expression (정규표현식) 모듈. 정규표현식이란? 문자열을 어떻게 처리할지에 대한 기준이 되는 표현 방식.
               # 예를 들어 어떤 문장에서 숫자만 골라내고 싶은 경우, 어떤 특정 문자가 반복되는 경우 바꾼다던가, 그 문자를 기준으로 나눈다던가 하는 것이다.
from urllib.parse import parse_qs    # URL 문자열을 구성 요소(주소 지정 체계, 네트워크 위치, 경로 등)으로 분리하고,
                                     # 구성 요소를 다시 URL 문자열로 결합하고, '상대 URL'을 절대 'URL'로 변환하는 표준 인터페이스를 정의.
import cgi    # Common Gateway Interface

PORT_NUMBER = 8090    # 전역변수. 어디선가 이걸 아래서 쓸거다.

# This class will handle any incoming request from
# a browser
class myHandler(BaseHTTPRequestHandler):    # Type : class, Name : myHandler, Super class : BaseHTTPRequestHandler (저 위에 form http.server import...)


    # Handler for the GET requests
    def do_GET(self):    # Override다. Super class인 BaseHTTPRequestHandler에 'do_GET' 함수가 있는데 Sub class인 myHandler에서 재정의해서 사용한다.
                         # 자바에서는 @Override를 붙여서 명시해줬지만 파이썬에서는 명시할 필요 없이 그냥 Super class에 있는 함수명을 그대로 가져다 다시 정의하면 된다.

        print('Get request received')
        if None != re.search('/api/v1/getrecord/*', self.path):

            recordID = (self.path.split('/')[-1]).split('?')[0]
            print("recordID = ", recordID)
            if recordID == "1" :
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                # Send the html message
                self.wfile.write(bytes("<html><head><title>Title goes here.</title></head>", "utf-8")) #"euc-kr"
                self.wfile.write(bytes("<body><p>This is a test.</p>", "utf-8"))
                self.wfile.write(bytes("<p>Your accessed path: %s</p>" % self.path, "utf-8"))    # %s% -> self.path가 요청한 경로 URL을 그대로 받을 수 있다.
                self.wfile.write(bytes("</body></html>", "utf-8"))

            else:
                self.send_response(400, 'Bad Request: record does not exist')
                self.send_header('Content-Type', 'application/json')
                self.end_headers()

    def do_POST(self):
        if None != re.search('/api/v1/addrecord/*', self.path):
            ctype, pdict = cgi.parse_header(self.headers['content-type']) # application/json;encoding=utf-8;lang=ko;loc=seoul;...
            print(ctype) # application/json
            print(pdict) # {encoding:utf-8, lang:ko, loc:seoul}

            if ctype == 'application/json':
                content_length = int(self.headers['Content-Length']) # 48 bytes
                post_data = self.rfile.read(content_length)    # .rfile.read( ) 읽어와라. (bytes로 읽어옴.)
                receivedData = post_data.decode('utf-8')    # .decode('utf-8')을 통해 우리가 아는 문자열로 바뀜.
                print(type(receivedData))
                tempDict = json.loads(receivedData) #  load your str into a dict    # 위에 문자열로 바꾼걸 다시 딕셔너리로 바꿈.
                #print(type(tempDict)) #print(tempDict['this'])
                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(tempDict), "utf-8"))    # 위에서 bytes -> str -> dic 으로 바꿔서 읽어들였던 것을 역순으로 해서 내보낸다. dic -> str -> bytes

            elif ctype == 'application/x-www-form-urlencoded':
                content_length = int(self.headers['content-length'])
                # trouble shooting, below code ref : https://github.com/aws/chalice/issues/355
                postvars = parse_qs((self.rfile.read(content_length)).decode('utf-8'),keep_blank_values=True)

                #print(postvars)    #print(type(postvars)) #print(postvars.keys())

                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                self.wfile.write(bytes(json.dumps(postvars) ,"utf-8"))
            else:
                self.send_response(403)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()

        else:
            self.send_response(404)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()

        # ref : https://mafayyaz.wordpress.com/2013/02/08/writing-simple-http-server-in-python-with-rest-and-json/


        return


try:

    # Create a web server and define the handler to manage the
    # incoming request
    server = HTTPServer(('', PORT_NUMBER), myHandler)    # HTTPServer : 위에 불러온 모듈, '서비스 할 IP' : 공란은 모든 IP에 대해서 서비스를 하겠다.
                                                         # PORT_NUMBER : 위에 있는 전역변수 8090, myHandler : BaseHTTPRequestHandler를 상속 받은 오버라이드가 들어갔다.(도움말 열어보면 얘가 들어가야 한다 나옴.)
                                                         # HTTPServer에 (괄호)안의 것들을 생성자로 넘겨주는거다. 그렇 server라는 객체를 생성한다.
    print ('Started httpserver on port ' , PORT_NUMBER)

    # Wait forever for incoming http requests
    server.serve_forever()    # server 라는 객체를 실행시킨다.

except:
    print ('^C received, shutting down the web server')
    print("서버 종료1!!")
    server.socket.close()    # 8090 포트를 닫아준다. (이거 안 닫으면 계속 점유하게된다.) 자원해제하는 절차.

 

ctype? pdict?

ctype;pdict1;pdict2;... ; 세미콜론으로 계속해서 붙일 수 있다.

 

 

cf. 출력을 찍는 방법의 차이

self.wfile.write(bytes(      , "utf-8")) : HTML 웹에 출력해준다.
print(      ) : 콘솔에 출력해준다.

 

+ Recent posts