Golang에는 웹 프레임워크가 여러가지 있습니다. 제가 공부했던 책에서는 Beego가 추천되었는데, 그 외에도 Revel, Martini, Buffalo, echo, iris 등 여러가지가 있습니다. 요즘은 gin이라는 프레임워크가 대세인 것 같아서 설치해볼까 합니다. https://github.com/gin-gonic/gin(Gin 소스 페이지) 우선 아래 명령어로 Gin을 설치합니다.
go get -u github.com/gin-gonic/gin
그리고 프로젝트를 생성해보겠습니다. 적당한 폴더를 하나 만들어 주고 Go 파일을 하나 작성해줍니다. 저는 그냥 main.go로 만들었습니다.
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
이제 웹 브라우저에서 localhost:8080/ping을 입력하면 메시지가 응답되는 것을 볼 수 있습니다.
보통 프로그램이 커질수록 파일을 기능별로 분리 관리를 하죠. Go 언어에서도 파일 분리/관리 방법이 있습니다만 오늘은 이것 때문에 삽질을 좀 많이 했네요. 역시 기초부터 제대로 공부하고 써먹어야한다는 생각을 절실히 체감하는 오늘입니다.
1. 같은 package 안에서는 함수, 메서드 등의 이름 첫글자가 소문자여도 사용이 가능합니다. (non-public)
폴더 구분없이 파일만 구분해서 사용하고 싶을 경우, 같은 패키지(현재 main)으로 하고, 함수이름 첫글자 소문자(my_plus)일 경우 문제없이 인식 가능합니다. (import 같은 기능 필요 없음)
2. 다른 패키지로 관리할 때, 즉 폴더별로 관리할 경우 주의해야 할 사항이 몇가지 있어서 정리를 할까 합니다.
1) 함수, 메서드 등의 이름 첫글자가 대문자이어야 사용 가능합니다.
2) 다음으로는 지금까지 제가 Golang을 잘못 설정해서 쓰고 있던 것일 수도 있는데, 프로그램 작성을 go_pkg/src 하위에 작성해야만 import 시 인식이 가능합니다. 다른 방법(기타 툴의 사용이라던가..)이 있는지는 잘 모르겠으나, 현재까지는 go_pkg/src가 아닌 외부 폴더에서 작업할 경우 인식이 안되는 것으로 확인됩니다.
화면 구성에 대해서는 최대한 자제하고 로직 구성에만 전념하겠습니다. 이에 대한 라우팅은 아래와 같이 구성합니다.
@app.route('/register', methods=['GET','POST'])
def register():
if request.method =='GET':
return render_template("register.html")
else:
userid = request.form.get('userid')
username = request.form.get('username')
password = request.form.get('password')
re_password = request.form.get('re_password')
if not (userid and username and password and re_password):
return "모두 입력해주세요"
elif password != re_password:
return "비밀번호를 확인해주세요"
else:
user = User()
user.password = password
user.userid = userid
user.username = username
db.session.add(user)
db.session.commit()
return "회원가입 완료"
return redirect('/')
최초 /register에 접속할 때에는 GET 방식으로 단순 html 파일을 보여주고, html 내 form 양식에 의해
<form method="POST" action="/register"> 로 불러질 때에는 POST 방식에 의해 수행이 됩니다. POST 방식에서는 아이디, 이름, 비번, 비번확인을 받아들여서, 하나라도 빠진게 있으면 체크하고, 비번과 비번확인이 다르면 체크하며, 모두 통과하면 users 테이블에 입력합니다. 입력이 완료되면 '/'으로 리다이렉트 됩니다.
4.login.html
로그인 페이지를 아래와 같이 구성합니다. 조금 뒤에 나오겠지만, 'userid'라는 session정보가 있을 경우 이미 로그인 했음을 알려주고, 그렇지 않을 경우 로그인 폼을 보여줍니다.
# login 페이지 접속(GET) 처리와, "action=/login" 처리(POST)처리 모두 정의
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method=='GET':
return render_template('login.html')
else:
userid = request.form['userid']
password = request.form['password']
try:
data = User.query.filter_by(userid=userid, password=password).first() # ID/PW 조회Query 실행
if data is not None: # 쿼리 데이터가 존재하면
session['userid'] = userid # userid를 session에 저장한다.
return redirect('/')
else:
return 'Dont Login' # 쿼리 데이터가 없으면 출력
except:
return "dont login" # 예외 상황 발생 시 출력
@app.route('/logout', methods=['GET'])
def logout():
session.pop('userid', None)
return redirect('/')
로그인 폼을 보여줄 'GET' 방식과, 로그인 처리(사용자 확인)를 위한 'POST'방식을 모두 정의합니다. GET이면 login.html을 곧바로 보여주고, POST방식일 경우 login.html 내 폼에서 userid와 password 부분을 가져와서 database 조회를 합니다. 조회가 성공하면 session['userid']에 폼으로부터 전달받은 userid값을 저장하고 '/'으로 이동합니다. 데이터가 없거나, 예외가 발생하면 그에 따른 처리를 각각 해 줍니다.
아울러 '/logout'에 대해서도 함께 정의해줍니다. 이 때에는 GET만 정의하면 됩니다.
5. __init__.py 수정
순서가 뒤죽박죽이 된 것 같네요. 위에서 세션을 사용한 코드를 이미 작성했는데.....한가지, Flask에서는 세션을 사용하려면 어플리케이션에 정의된 시크릿키(SECRET_KEY)가 필요합니다. 이 키로 서명된 쿠키가 사용된다고 합니다. 꼭 잊지말고 넣어주도록 합시다.
그밖에 기존에 사용자 인증과 관련없는 라우팅은 지면관계상 지우도록 하겠습니다.
@app.route('/home')도 지워버리고, 대신 home.html을 '/' 에서 라우팅하도록 하였습니다. 단 session['userid']가 있을 경우와 없을 경우를 나누어 처리하도록 하였습니다.
from flask import Flask, render_template, request, redirect, session, url_for
from flask_sqlalchemy import SQLAlchemy
from helloflask.model.user_model import Member, User
app = Flask(__name__)
app.secret_key="123123123"
# database 설정파일
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:1234@localhost:3306/testdb"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
@app.route('/')
def home():
#로그인 세션정보('userid')가 있을 경우
if not session.get('userid'):
return render_template('home.html')
#로그인 세션정보가 없을 경우
else:
userid = session.get('userid')
return render_template('home.html', userid=userid)
# @app.route('/home') 삭제
# @app.route('/one') 삭제
# @app.route('/all') 삭제
# @app.route('/search', methods=['POST','GET']) 삭제
# register 페이지 접속(GET) 처리와, "action=/register" 처리(POST) 모두 정의
@app.route('/register', methods=['GET','POST'])
def register():
if request.method =='GET':
return render_template("register.html")
else:
userid = request.form.get('userid')
username = request.form.get('username')
password = request.form.get('password')
re_password = request.form.get('re_password')
if not (userid and username and password and re_password):
return "모두 입력해주세요"
elif password != re_password:
return "비밀번호를 확인해주세요"
else:
user = User()
user.password = password
user.userid = userid
user.username = username
db.session.add(user)
db.session.commit()
return "회원가입 완료"
return redirect('/')
# login 페이지 접속(GET)처리와, "action=/login" 처리(POST)모두 정의
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method=='GET':
return render_template('login.html')
else:
userid = request.form['userid']
password = request.form['password']
try:
data = User.query.filter_by(userid=userid, password=password).first() # ID/PW 조회Query 실행
if data is not None: # 쿼리 데이터가 존재하면
session['userid'] = userid # userid를 session에 저장한다.
return redirect('/')
else:
return 'Dont Login' # 쿼리 데이터가 없으면 출력
except:
return "dont login" # 예외 상황 발생 시 출력
@app.route('/logout', methods=['GET'])
def logout():
session.pop('userid', None)
return redirect('/')
6. home.html 수정
userid 세션이 존재할 경우 logout 버튼이 나오도록 처리하고, userid 세션이 없을 경우에는 등록하기와 로그인 버튼이 나타나도록 처리했습니다.
form 으로부터 전달된 이름('nm')을 받아서 temp 변수에 저장하고, query.filter_by로 DB에서 조회한 후, 결과를 members로 저장합니다. 그리고 members의 갯수를 count에 저장합니다. render_template함수를 통해 db.html로 연결이 되는데, 이때 members와 count를 함께 넘겨줍니다.
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from helloflask.model import Members
from datetime import datetime
app = Flask(__name__)
# database 설정파일
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:1234@localhost:3306/testdb"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
@app.route("/")
def home():
member = Members.query.first()
return 'Hello {0}, {1}, {2}, {3}, {4}'\
.format(member.name, member.email, member.phone, member.start.isoformat(), member.end.isoformat())
#return render_template('home.html')
@app.route('/all')
def select_all():
members = Members.query.all()
return render_template('db.html', members=members)
@app.route('/search', methods=['POST','GET'])
def calculate():
if request.method=='POST':
#temp = request.args.get('nm')
temp = request.form['nm']
members = Members.query.filter_by(name=temp)
count=members.count()
return render_template('db.html', members=members, cnt=count)
2.db.html
form 입력은 "POST" 형태로 이루어지고, 처리를 한 action을 명시해줍니다. 이후 "/search"에서 처리한 결과는 members 변수에 담아 전달됩니다. members 변수의 각 attribute를 출력하는 부분을 작성합니다.
이번 강좌에서는 ORM 방식으로 Database와 연동하는 방법에 대해 포스팅하겠습니다. ORM은 Object-Relational Mapping 이라고 하여, Database 객체를 객체지향 프로그래밍 언어로 변환하는 기법입니다. (자세한 내용은 다른 포스팅들을 참조하시기 바랍니다.) 그 중에서도 Flask에서 ORM을 구현할 수 있게 해주는 라이브러리가 Flask-SQLAlchemy 입니다. 이 Flask-SQLAlchemy를 이용하여 MySql과 연동해보도록 하겠습니다.
1. Database 자료 생성
아래와 같이 id, name, email, phone, start(datetime), end(datetime)을 필드로 하는 자료를 생성해놓습니다.
2. 라이브러리 설치
먼저 말했듯이 이번 강좌의 메인인 flask_sqlalchemy 라이브러리를 설치합니다. 그리고 mysql과 연동하기 위한 pymysql라이브러리도 함께 설치해줍니다.
pip install flask_sqlalchemy
pip install pymysql
3. 앱 구성
이번 helloflask앱은 아래와 같은 구성으로 만들려고 합니다. DB연동은 __init__.py에서 구현하고 db.html에서 결과를 표시합니다.
3. model.py
연결된 Database의 table을 받아들일 class를 정의합니다.
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class Members(db.Model):
""" table name : members
table info
- id : index id
- name
- start: start datetime
- end: end datetime """
__tablename__ = 'members'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(20, 'utf8mb4_unicode_ci'))
email = db.Column(db.String(50, 'utf8mb4_unicode_ci'))
phone = db.Column(db.String(20, 'utf8mb4_unicode_ci'))
start = db.Column(db.DateTime, default=datetime.utcnow())
end = db.Column(db.DateTime, default=datetime.utcnow())
def __init__(self, name, email, phone, start, end):
self.name = name
self.email = email
self.phone = phone
self.start = start
self.end = end
4. __init__.py
예제 구성을 단순화하기 위해 database설정부분을 __init__.py 파일 안에 포함시켰습니다. 본인의 Database/ID/Password 등에 맞게 설정을 하시면 됩니다. one만 출력할 때에는 string을 return시켰으며, all을 출력할 때에는 db.html에 출력하도록 구성하였습니다.
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from helloflask.model.user_model import Members
app = Flask(__name__)
# database 설정파일
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:1234@localhost:3306/testdb"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
@app.route('/home')
def home():
return render_template('home.html')
@app.route("/one")
def home():
member = Members.query.first()
return 'Hello {0}, {1}, {2}, {3}, {4}'\
.format(member.name, member.email, member.phone, member.start.isoformat(), member.end.isoformat())
#return render_template('home.html')
@app.route('/all')
def select_all():
members = Members.query.all()
return render_template('db.html', members=members)