Flask 서버 구축 - Flask seobeo guchug

웹 어플리케이션을 만든다면 과거의 선택지는 PHP,JSP,ASP 세가지 정도였다.

그러나 시대가 바뀌면서 코딩 패러다임 역시 바뀌었다.

웹 역시 이 변화를 피해갈순 없었고 PHP,JSP,ASP에서 생각하는 방식은 구식이되고

새로운 방식으로  flask나 node.js등이 뜨고 있다.

이제는 이 들을 무시할 수 없는 수준까지 오게 되었고 이번에는 간단하게 서버를 만드는 것을 하려한다.

flask 서버 만들기

flask 서버를 만들기는 매우 쉬운 일이다.

일단 python이 필요하다. 근데 웃긴건 파이썬만 있으면된다.

뭔가 하기위해서 준비 동작이 필요한 JSP같은 것들과는 달리 서버역시 우리가 만든다.

IDE를 써도 좋고 안 써도 좋으며, 그냥 텍스트에디터를 써도좋다.

필자가 이번에 쓰는건  PyCharm이라는 IDE이며 파이썬계의 혁명과도 같은 IDE이다.

그 버전중에 유료버전을 사용하면 아래와 같은 화면을 볼 수 있다.

Flask 서버 구축 - Flask seobeo guchug

무료버전은 왼쪽 선택창이없는데 사실 flask는 복잡한 구조가아니므로 무료버전으로 사용해도 문제는 없다.

필자는 유료버전을 사용중이며 Flask의 디폴트 구조를 만들어주는 Flask탭을 선택했지만

필자의 원래 컴퓨터는 무료버전으로 깔려있고 사용하는데 아무지장없으므로 그냥 따라하면된다.

설치하면 자동으로 코드가 생성된다.

생성된 코드는 아래와 같다.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello World!'


if __name__ == '__main__':
app.run()

뭔 내용인지 하나도 모를것이다.

뭐 당연하다. 안다면 이 강의 보고 있을리가 없을테니까.

저 짧은 코드들로 여러분은 이미 서버를 완성했다.

그렇다. 여러분은 아무것도 한게없는데 서버를 완성한 셈이다.

그럼 정말 완성된게 맞을까?? 실행을 해보자.

if __name__ == '__main__':

이 구문옆에 재생버튼이 생겼을 것이다.

만약 무료버전이거나 터미널에서한다면 명령어를 실행시키면된다.

맥 및 리눅스

$python3 untitled.py

윈도우즈

>py untitled.py

 그러면 서버가 실행됨을 로그로 알 수 있다. 해당 로그에 하이퍼링크가 되어있다.

localhost:5000을 눌러줘도 좋고 타이핑해도 좋다.

정말 서버가 실행됨을 알 수 있다.

이제 코드를 해석해보자.

코드의 해석

이 코드는 정말 내용이 없는 간단한 코드다.

사실 알고나면 허탈할정도로 내용이없다.

먼저 main코드를 보자.

if __name__ == '__main__':
app.run()

이 코드는 app.run을 실행시키는 코드이다.

해석할 필요도 없는 파이썬 기본중에 기본이다. 즉 main부분의 run으로 서버는 동작케 된다.

@app.route('/')
def hello_world():
return 'Hello World!'

중요한건 이부분으로 route모델이다.

이 부분은 현대 웹프로그래밍에서 가장 중요한 것인데 위에는 데커레이터가 선언되어있다.

이 구문의 데커레이터가 어떻게 되있는지는 알필요가 없다.

route는 경로라는 뜻이 있듯이 사용자에게 경로를 안내해준다고 생각하면된다.

즉 경로/는 루트이므로 가장 기본경로이다.

즉 우리의 주소에서 가장 기본되는경로, 즉 아무 경로를 입력하지 않을경우 나오는 디폴트 경로라는 것이다.

그 결과 return 값은 화면에 뿌려줄 값이다.

이 말을 좀더 프로그래밍적이게 설명을 하자면 누군가 /라는 경로를 요청했으며

그 요청에 대한 답으로 Hello World를 보여주겠다는것이다.

중요한건 여기 return값은 html 그자체라는 것이다.

구문을 살짝 바꿔보자.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
return '<h2>Hello World!</h2><input type="textbox"/>'


if __name__ == '__main__':
app.run()

해당 구문 실행시 결과는 여러분의 예상대로 나온다.

route는 조금더 자세히 해볼 필요가 있으므로 아래의 예를 추가로 보자.

route

route는 페이지의경로를 설정한다.

하나의 웹사이트는 여러개의 페이지로 되어있다.

사실 페이지라고 말하는게 요즘 패러다임에는 맞는지는 잘 모르겠지만.

따라서 아래와 같은 코딩역시 가능하다.

from flask import Flask

app = Flask(__name__)

@app.route('/start')
def start():
return 'start'


@app.route('/select/<name>')
def select(name):
return 'hi %s' % name

@app.route('/')
def hello_world():
return '<h2>Hello World!</h2><input type="textbox"/>'


if __name__ == '__main__':
app.run()

해당 예제를 파악하고 기초적인 html을 안다면 이제 웹페이지 제작은 대부분 파악했다고 봐도 문제없을 것이다.

예제의 결과만 봐도 해당 코드들이 뭘 의미하는지 알 수있을것이다.

위의 예처럼 경로의 명을 함수의 파라메터로 받아서 반환값을 사용하는것 역시 가능하며

경로의 명을 사용하는것 역시 얼마든지 가능한것을 알 수 있다.

Flask 서버 구축 - Flask seobeo guchug

Flask는 Python으로 작성된 웹 프레임워크로 간단한 기능과 빠른 설치 및 사용이 장점이다. 물론 기능을 덧붙인다면 충분히 복잡한 웹 애플리케이션을 작성할 수 있지만 다른 라이브러리나 의존성을 강요하지 않기 때문에 가볍게 시작할 수 있다.

설치

Flask를 설치하는 방법은 단순히 pip install flask처럼 pip를 이용하면 된다.

Flask 서버 구축 - Flask seobeo guchug

Flask는 Werkzeug와 Jinja라는 다른 파이썬 웹 프레임워크에 대한 래퍼에서 탄생했기 때문에 Flask를 설치하면 자동으로 이 두 프레임워크도 같이 설치된다. 이 모듈을 따로 임포트해서 사용할 필요는 없고 아래처럼 Flask 자체만 임포트하여 사용할 수 있다.

from flask import Flask, escape, request

app = Flask(__name__)

@app.route('/')
def hello():
    name = request.args.get("name", "World")
    return f'Hello, {escape(name)}!'

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Flask를 활용하여 간단하게 웹 서버를 구축하고자 한다면 크게 다음과 같은 3가지가 필요하다.

  • Flask 클래스의 인스턴스 생성

  • route() 데코레이터를 이용한 URL 처리 함수 지정

  • Flask 인스턴스에 호스트 주소, 포트 넘버를 지정한 후 run() 함수 호출

문서에서는 조금 다른 방법을 이용해서 웹서버를 동작시키고 있기 때문에 app.run() 호출 구문이 없지만 이 파이썬 파일을 실행시켰을 때 서버가 동작하게 만들고 싶다면 아래처럼 이 파이썬 파일이 직접 실행됐을 때, 즉 모듈로 임포트된 게 아닐 때(__name__ == "__main__") 0.0.0.0:5000 주소로 Flask 인스턴스를 실행한다.

Flask 서버 구축 - Flask seobeo guchug

위와 같은 요청의 경우 최상위 디렉토리('/')에 요청이 들어왔기 때문에 해당 URL에 대한 처리기인 hello() 함수에서 반환한 값이 브라우저에 출력되는 것을 볼 수 있다.

사용자 요청 수신

사용자가 웹서버에 데이터를 요청하거나 전달할 때는 GET이나 POST로 수행할 수 있다. 그렇지만 Rest API인지 뭔지 하면서 유행하는 것처럼 사용자가 URL에 어떤 값을 넣어서 요청한다면 어떨까? 이를 수신하기 위해 Flask에서는 URL 변수를 사용할 수 있다.

@app.route('/calculator/<string:operation>/<int:value1>/<int:value2>')
def calculator(operation, value1, value2):
    ret = 0
    if operation == "add":
        ret = value1 + value2
    elif operation == "sub":
        ret = value1 - value2
    elif operation == "mul":
        ret = value1 * value2
    elif operation == "div":
        ret = value1 / value2

    return f'Operation {operation} with {value1} and {value2} is {ret}'

사용자가 URL에 입력하는 부분을 '/'로 구분해서 꺽쇠(<, >)로 감싸면 변수처럼 사용할 수 있는데 route 처리 함수(calculator)에서 매개변수로 받아와야 내부 파이썬 코드에서 사용할 수 있다. 필수는 아니지만 string('/'가 없는 문자열)으로 지정된 operation, int로 지정된 value1, value2처럼 데이터 타입을 지정할 수 있는데 이는 값을 변환시키는 역할이기 때문에 만약 변환할 수 없는 값이 입력된다면 이 route에서 처리하지 않는다.

Flask 서버 구축 - Flask seobeo guchug

위와 같은 경우 value1, value2에 1, 2가 전달됐다면 int 자료형으로 변환됐기 때문에 calculator() 함수에서 이를 처리할 수 있었으나 hello, world가 전달된 경우 이는 int 자료형으로 변환될 수 없기 때문에 calculator() 함수로 route되지 않아 결국 처리기를 찾지 못해 404 Not Found 에러가 발생했다. 그렇기 때문에 다음과 같은 컨버터 타입을 잘 보고 route를 지정해줘야 할 것이다.

Flask 서버 구축 - Flask seobeo guchug
  • string: '/'를 제외한 모든 텍스트

  • int: 자연수(양수인 정수)

  • float: 양수인 실수

  • path: '/'도 받을 수 있는 string

  • uuid: UUID 문자열

유의할 것은 route할 URL 맨 마지막에 슬래시('/')가 있느냐 없느냐에 따라 처리가 달라진다는 것이다.

@app.route('/<notrail>')
def notrail(notrail):
    return "notrail"

@app.route('/<trail>/')
def trail(trail):
    return "trail"
Flask 서버 구축 - Flask seobeo guchug

이처럼 응답이 다르기 때문에 실수로 슬래시를 붙이거나 빼먹는 일이 없도록 하자.

URL 변수가 아니라 많이 보는 것처럼 URL 파라미터로 값이 넘어온다면 request 모듈을 이용해 URL 파라미터에서 해당 인자에 대해 기본값을 포함하여 값을 받아오는 것이 가능하다.

@app.route('/information')
def information():
    users = [{'name':'John Smith', 'workplace':'School', 'userid':'10011'},
              {'name':'U.N. Owen', 'workplace':'DoA', 'userid':'10021'},
              {'name':'Guest', 'workplace':'None', 'userid':'10001'}]
    
    name = request.args.get("name", "Guest")
    workplace = request.args.get("workplace", "None")

    for user in users:
        if user['name'] == name and user['workplace'] == workplace:
            return f'Hello, {name}#{user["userid"]}!'

    return 'Who are you?'
Flask 서버 구축 - Flask seobeo guchug

request 모듈의 args 변수는 "?var1=val1&var2=val2" 같은 URL 파라미터를 파싱한 결과값을 사전으로 가지고 있으며 이 사전에 대해 get() 메소드로 해당하는 파라미터를 찾아서 얻어올 수 있다. 해당 결과값이 없을 경우를 대비해 기본값("Guest", "None" 등)을 지정할 수 있으며 이렇게 얻은 파라미터는 내부 로직에서 사용될 수 있다.

위에서 설명한 접근 방법들은 모두 GET 메소드에 해당되는 것들이고 바디에 데이터를 포함해서 보내는 POST 메소드의 경우 어떻게 처리할 수 있을까? 이는 request 모듈의 form 변수에서 참조할 수 있다.

@app.route('/information', methods=['GET', 'POST'])
def information():
    users = [{'name':'John Smith', 'workplace':'School', 'userid':'10011'},
              {'name':'U.N. Owen', 'workplace':'DoA', 'userid':'10021'},
              {'name':'Guest', 'workplace':'None', 'userid':'10001'}]
    method = ''
    if request.method == 'GET':
        name = request.args.get("name", "Guest")
        workplace = request.args.get("workplace", "None")
        method = 'GET'
        
    elif request.method == 'POST':
        name = request.form['name']
        workplace = request.form['workplace']
        method = 'POST'

    for user in users:
        if user['name'] == name and user['workplace'] == workplace:
            return f'Hello, {name}#{user["userid"]} by {method}!'

    return 'Who are you?'

위의 information() 핸들러를 조금 수정하여 사용자가 이 URL에 접근하는 HTTP Method가 GET인지, POST인지에 따라 다른 처리를 수행하도록 구현하였다. 따로 폼은 구현하지 않고 Postman을 통해 POST 요청을 보냈는데 원래라면 HTML의 input 태그 등을 활용해서 form 태그에서 POST 방식으로 전송하는 것이 일반적이다.

Flask 서버 구축 - Flask seobeo guchug

바디에 name=John%20Smith&workplace=School처럼 전달되었을 것이며 information() 핸들러에서 POST 분기를 타서 출력 맨 마지막에 POST가 포함된 것을 통해 확인할 수 있다.

사용자 요청에 대한 응답

사용자 요청에 대한 응답을 반환하는 것이기 때문에 이를 반환하지 않을 경우 사용자에게는 아무런 출력도 돌아가지 않는다. 그래서 어떤 템플릿이든 문자열이든 json 데이터든 꼭 return으로 반환해줘야 한다.

Flask 서버 구축 - Flask seobeo guchug
he view function did not return a valid response. The function either returned None or ended without a return statement.

그런데 단순 문자열이나 어떤 값이 아니라 사이트 자체를 반환하려면 어떻게 해야 할까? 일일히 모든 태그와 설정값을 적어서 반환해주면 대충 HTML 문서 구조는 만들 수 있겠지만 코드 내용이 엄청 길어질것이고 가독성이나 유지보수도 불편할 것이다.

Flask 서버 구축 - Flask seobeo guchug
h2, p 태그를 직접 사용

그래서 사용할 수 있는 모듈이 render_template이다. 이름에서부터 알 수 있듯이 이는 HTML 문서 템플릿에 값만 넣어서 HTML 문서를 동적으로 생성하여 반환해줄 수 있는 기능이다. 이를 사용하려면 템플릿이 될 HTML 문서에 다음처럼 미리 코드가 작성되어 있어야 한다.

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h2>Hello {{ name }}!</h2>
{% else %}
  <h2>Hello, World!</h2>
{% endif %}

이렇게 작성된 HTML 문서는 templates라는 폴더 내부에 위치해야 한다. 그래야 render_template() 함수 호출의 매개변수로 사용될 수 있으며 필요한 매개변수(위의 코드에서는 name)들도 호출 시 같이 전달되어야 한다.

@app.route('/hello')
def template_hello():
    return render_template('hello_template.html', name='John Smith')
Flask 서버 구축 - Flask seobeo guchug

이외에도 {% for %} ~ {% endfor %}이나 다른 템플릿도 있으니 이는 문서를 참고해서 응용할 수 있다.

Template Designer Documentation — Jinja Documentation (2.11.x)

This document describes the syntax and semantics of the template engine and will be most useful as reference to those creating Jinja templates. As the template engine is very flexible, the configuration from the application can be slightly different from t

jinja.palletsprojects.com

서버 띄우기

어쨌든 이렇게 파이썬으로 작성된 코드를 AWS나 기타 호스팅을 통해서 24시간 켜놓아야 할 일이 있을 텐데 이는 문서에 나온 방법을 따라할 수도 있지만 제일 간단한 것은 리눅스 환경일 경우 nohup 명령어를 사용하는 것이다.

nohup python -u webserver.py &

이는 쉘 명령어를 사용한 방법으로 SSH로 서버에 로그인하거나 기타 방법을 통해 쉘에 접속한 후 다음과 같이 명령어를 실행해주면 서버를 끄거나 직접 프로세스를 종료하지 않는이상 로그아웃해도 파이썬 프로세스가 꺼지지 않는다. 자세한 설명은 이곳 참조.

Flask - nohup으로 백그라운드 실행하기

nohup과 &로 파이썬 플라스크 웹 서버를 백그라운드로 실행하기

wooiljeong.github.io

Flask 서버 구축 - Flask seobeo guchug

기타

이 포스트에 적힌 내용 말고도 에러 핸들링(404 에러 등), 파일 전송 및 수신, 리디렉션, URL 빌딩 등 여러 유용한 기능을 플라스크를 통해 사용할 수 있는데 이는 공식 문서를 참고하자.

Quickstart — Flask Documentation (1.1.x)

For web applications it’s crucial to react to the data a client sends to the server. In Flask this information is provided by the global request object. If you have some experience with Python you might be wondering how that object can be global and how

flask.palletsprojects.com