ฉันรักและใช้ Django ในโครงการส่วนบุคคลและลูกค้าของฉันจำนวนมากส่วนใหญ่เป็นเว็บแอปพลิเคชันแบบคลาสสิกและที่เกี่ยวข้องกับฐานข้อมูลเชิงสัมพันธ์ อย่างไรก็ตาม Django ไม่ใช่กระสุนเงิน
จากการออกแบบ Django นั้นควบคู่ไปกับวัตถุ ORM, Template Engine System และ Settings อย่างแน่นหนา นอกจากนี้ยังไม่ใช่โครงการใหม่เนื่องจากมีสัมภาระจำนวนมากเพื่อให้สามารถใช้งานร่วมกับถอยหลังได้
นักพัฒนา Python บางคนมองว่านี่เป็นปัญหาสำคัญ พวกเขากล่าวว่า Django ไม่มีความยืดหยุ่นเพียงพอและหลีกเลี่ยงถ้าเป็นไปได้และใช้ไมโครเฟรมเวิร์ค Python เช่น Flask แทน
ฉันไม่แบ่งปันความคิดเห็นนั้น Django นั้นยอดเยี่ยมเมื่อใช้ในไฟล์ สถานที่และเวลาที่เหมาะสม แม้ว่าจะไม่เข้ากับ ทุกๆ ข้อมูลจำเพาะโครงการ ตามที่ต้องมนต์:“ ใช้เครื่องมือที่เหมาะสมกับงาน”
(แม้ว่าจะไม่ใช่สถานที่และเวลาที่เหมาะสม แต่บางครั้งการเขียนโปรแกรมด้วย Django ก็มีประโยชน์ที่ไม่เหมือนใคร)
การจัดทำงบประมาณทุนคือกระบวนการ
ในบางกรณีการใช้เฟรมเวิร์กที่มีน้ำหนักเบากว่านั้นอาจเป็นเรื่องดี (เช่น กระติกน้ำ ). บ่อยครั้งที่ไมโครเฟรมเวิร์กเหล่านี้เริ่มฉายแววเมื่อคุณรู้ว่าการแฮ็กทำได้ง่ายเพียงใด
ในโครงการของลูกค้าของฉันบางโครงการเราได้พูดถึงการยอมแพ้ Django และย้ายไปทำไมโครเฟรมเวิร์คโดยทั่วไปเมื่อลูกค้าต้องการทำสิ่งที่น่าสนใจ (ในกรณีหนึ่งเช่นการฝัง ZeroMQ ในแอปพลิเคชันออบเจ็กต์) และเป้าหมายของโครงการดูเหมือนยากกว่าที่จะบรรลุด้วย Django
โดยทั่วไปแล้วฉันพบว่า Flask มีประโยชน์สำหรับ:
ในขณะเดียวกันแอพของเราต้องการการลงทะเบียนผู้ใช้และงานทั่วไปอื่น ๆ ที่ Django แก้ไขเมื่อหลายปีก่อน ด้วยน้ำหนักที่เบากระติกน้ำจึงไม่ได้มาพร้อมกับชุดเครื่องมือเดียวกัน
เกิดคำถาม: Django เป็นข้อตกลงทั้งหมดหรือไม่มีอะไรเลย?เกิดคำถาม: Django เป็นข้อตกลงทั้งหมดหรือไม่มีอะไรเลย? เราควรปล่อยวาง สมบูรณ์ จากโครงการหรือเราสามารถเรียนรู้ที่จะรวมเข้ากับความยืดหยุ่นของไมโครเฟรมเวิร์คอื่น ๆ หรือเฟรมเวิร์กแบบเดิมได้หรือไม่? เราสามารถเลือกและเลือกชิ้นส่วนที่เราต้องการใช้และหลีกเลี่ยงผู้อื่นได้หรือไม่?
เราจะมีสิ่งที่ดีที่สุดของทั้งสองโลกได้หรือไม่? ฉันตอบว่าใช่โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการจัดการเซสชัน
(ไม่ต้องพูดถึงมีโครงการมากมายสำหรับ Django freelancers)
google maps ได้กำหนดมาตรฐานใหม่สำหรับการแสดงข้อมูลด้วยซอฟต์แวร์การทำแผนที่เว็บที่ใช้งานง่าย
เป้าหมายของโพสต์นี้คือการมอบหมายงานการตรวจสอบผู้ใช้และการลงทะเบียนให้กับ Django แต่ใช้ Redis เพื่อแชร์เซสชันของผู้ใช้กับเฟรมเวิร์กอื่น ๆ ฉันนึกถึงสถานการณ์บางอย่างที่สิ่งนี้จะเป็นประโยชน์:
สำหรับบทแนะนำนี้ฉันจะใช้ Redis เพื่อแชร์เซสชันระหว่างสองเฟรมเวิร์ก (ในกรณีนี้คือ Django และ Flask) ในการตั้งค่าปัจจุบันฉันจะใช้ SQLite เพื่อจัดเก็บข้อมูลผู้ใช้ แต่คุณสามารถให้ส่วนหลังของคุณเชื่อมโยงกับฐานข้อมูล NoSQL (หรือทางเลือกที่ใช้ SQL) ได้หากจำเป็น
ในการแบ่งปันเซสชันระหว่าง Django และ Flask เราจำเป็นต้องรู้เล็กน้อยเกี่ยวกับวิธีที่ Django จัดเก็บข้อมูลเซสชัน เอกสาร Django ค่อนข้างดี แต่ฉันจะให้ข้อมูลเบื้องหลังเพื่อความสมบูรณ์
โดยทั่วไปคุณสามารถเลือกจัดการข้อมูลเซสชันของแอป Python ได้ด้วยวิธีใดวิธีหนึ่ง:
เซสชันที่ใช้คุกกี้ : ในสถานการณ์สมมตินี้ข้อมูลเซสชันจะไม่ถูกเก็บไว้ในที่เก็บข้อมูลที่ส่วนหลัง แต่จะทำให้เป็นอนุกรมลงนาม (ด้วย SECRET_KEY) และส่งไปยังลูกค้า เมื่อไคลเอนต์ส่งข้อมูลกลับมาระบบจะตรวจสอบความสมบูรณ์ของข้อมูลเพื่อหาการปลอมแปลงและจะถูกยกเลิกการกำหนดค่าเดิมอีกครั้งบนเซิร์ฟเวอร์
เซสชันที่ใช้พื้นที่เก็บข้อมูล : ในสถานการณ์นี้ข้อมูลเซสชันเองคือ ไม่ ส่งไปยังลูกค้า แต่จะมีการส่งเพียงส่วนเล็ก ๆ (คีย์) เพื่อระบุตัวตนของผู้ใช้ปัจจุบันที่เก็บไว้ในที่เก็บเซสชัน
ในตัวอย่างของเราเราสนใจสถานการณ์หลังมากกว่านี้: เราต้องการให้ข้อมูลเซสชันของเราถูกเก็บไว้ที่ส่วนหลังจากนั้นจึงตรวจสอบใน Flask สิ่งเดียวกันนี้สามารถทำได้ในอดีต แต่ตามที่เอกสารของ Django กล่าวถึงมีบางอย่าง ความกังวลเกี่ยวกับความปลอดภัย ของวิธีแรก
ขั้นตอนการทำงานทั่วไปของการจัดการและการจัดการเซสชันจะคล้ายกับแผนภาพนี้:
เรามาดูการแบ่งปันเซสชันในรายละเอียดเพิ่มเติมเล็กน้อย:
เมื่อมีคำขอใหม่เข้ามาขั้นตอนแรกคือการส่งผ่านผู้ลงทะเบียน มิดเดิลแวร์ ในกอง Django เราสนใจที่นี่ใน SessionMiddleware
คลาสตามที่คุณคาดหวังเกี่ยวข้องกับการจัดการและการจัดการเซสชัน:
ความแตกต่างระหว่าง llc s corp และ c corp
class SessionMiddleware(object): def process_request(self, request): engine = import_module(settings.SESSION_ENGINE) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) request.session = engine.SessionStore(session_key)
ในตัวอย่างนี้ Django คว้าผู้ลงทะเบียน SessionEngine
(เราจะไปถึงมันเร็ว ๆ นี้) แยก SESSION_COOKIE_NAME
จาก request
(sessionid
โดยค่าเริ่มต้น) และสร้างอินสแตนซ์ใหม่ของ SessionEngine
ที่เลือก เพื่อจัดการการจัดเก็บเซสชัน
ในภายหลัง (หลังจากประมวลผลมุมมองของผู้ใช้แล้ว แต่ยังอยู่ในสแต็กมิดเดิลแวร์) กลไกเซสชันจะเรียกใช้วิธีการบันทึกเพื่อบันทึกการเปลี่ยนแปลงใด ๆ ในที่เก็บข้อมูล (ในระหว่างการจัดการมุมมองผู้ใช้อาจมีการเปลี่ยนแปลงบางอย่างภายในเซสชันเช่นโดยการเพิ่มค่าใหม่ให้กับวัตถุเซสชันด้วย request.session
) จากนั้น SESSION_COOKIE_NAME
ถูกส่งไปยังลูกค้า นี่คือเวอร์ชันที่เรียบง่าย:
def process_response(self, request, response): .... if response.status_code != 500: request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response
เราสนใจเป็นพิเศษใน SessionEngine
คลาสซึ่งเราจะแทนที่ด้วยสิ่งที่ใช้จัดเก็บและโหลดข้อมูลเข้าและออกจากส่วนหลังของ Redis
โชคดีที่มีไม่กี่โครงการที่จัดการเรื่องนี้ให้เราแล้ว นี่คือตัวอย่างจาก redis_sessions_fork . ให้ความสำคัญกับ save
และ load
วิธีการซึ่งเขียนขึ้นเพื่อ (ตามลำดับ) จัดเก็บและโหลดเซสชันเข้าและออกจาก Redis:
class SessionStore(SessionBase): ''' Redis session back-end for Django ''' def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def _get_or_create_session_key(self): if self._session_key is None: self._session_key = self._get_new_session_key() return self._session_key def load(self): session_data = backend.get(self.session_key) if not session_data is None: return self.decode(session_data) else: self.create() return {} def exists(self, session_key): return backend.exists(session_key) def create(self): while True: self._session_key = self._get_new_session_key() try: self.save(must_create=True) except CreateError: continue self.modified = True self._session_cache = {} return def save(self, must_create=False): session_key = self._get_or_create_session_key() expire_in = self.get_expiry_age() session_data = self.encode(self._get_session(no_load=must_create)) backend.save(session_key, expire_in, session_data, must_create) def delete(self, session_key=None): if session_key is None: if self.session_key is None: return session_key = self.session_key backend.delete(session_key)
สิ่งสำคัญคือต้องทำความเข้าใจว่าคลาสนี้ทำงานอย่างไรเนื่องจากเราจะต้องใช้สิ่งที่คล้ายกันใน Flask เพื่อโหลดข้อมูลเซสชัน มาดูตัวอย่าง REPL กันดีกว่า:
>>> from django.conf import settings >>> from django.utils.importlib import import_module >>> engine = import_module(settings.SESSION_ENGINE) >>> engine.SessionStore() >>> store['count'] = 1 >>> store.save() >>> store.load() {u'count': 1}
อินเทอร์เฟซของร้านเซสชันค่อนข้างเข้าใจง่าย แต่มีหลายสิ่งที่เกิดขึ้นภายใต้ประทุน เราควรเจาะลึกลงไปอีกหน่อยเพื่อที่เราจะได้นำสิ่งที่คล้ายกันไปใช้กับ Flask
หมายเหตุ: คุณอาจถามว่า“ ทำไมไม่แค่คัดลอก SessionEngine ลงใน Flask ล่ะ” พูดง่ายกว่าทำ. ดังที่เราได้กล่าวไปแล้วในตอนต้น Django นั้นควบคู่ไปกับออบเจ็กต์การตั้งค่าอย่างแน่นหนาดังนั้นคุณจึงไม่สามารถนำเข้าโมดูล Django และใช้งานได้โดยไม่ต้องทำงานเพิ่มเติม
อย่างที่ฉันพูดไป Django ทำงานหลายอย่างเพื่อปกปิดความซับซ้อนของพื้นที่จัดเก็บเซสชัน มาตรวจสอบคีย์ Redis ที่เก็บไว้ในตัวอย่างด้านบน:
>>> store.session_key u'ery3j462ezmmgebbpwjajlxjxmvt5adu'
ตอนนี้ให้ค้นหาคีย์นั้นบน redis-cli:
redis 127.0.0.1:6379> get 'django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu' 'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=='
สิ่งที่เราเห็นอยู่นี้มันยาวนานมาก เข้ารหัส Base64 สตริง เพื่อให้เข้าใจจุดประสงค์เราต้องดูที่ SessionBase
ของ Django ชั้นเรียนเพื่อดูวิธีจัดการ:
class SessionBase(object): ''' Base class for all Session classes. ''' def encode(self, session_dict): 'Returns the given session dictionary serialized and encoded as a string.' serialized = self.serializer().dumps(session_dict) hash = self._hash(serialized) return base64.b64encode(hash.encode() + b':' + serialized).decode('ascii') def decode(self, session_data): encoded_data = base64.b64decode(force_bytes(session_data)) try: hash, serialized = encoded_data.split(b':', 1) expected_hash = self._hash(serialized) if not constant_time_compare(hash.decode(), expected_hash): raise SuspiciousSession('Session data corrupted') else: return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions if isinstance(e, SuspiciousOperation): logger = logging.getLogger('django.security.%s' % e.__class__.__name__) logger.warning(force_text(e)) return {}
วิธีการเข้ารหัสจะทำให้ข้อมูลเป็นอนุกรมกับซีเรียลไลเซอร์ที่ลงทะเบียนปัจจุบันก่อน กล่าวอีกนัยหนึ่งคือจะแปลงเซสชันเป็นสตริงซึ่งสามารถแปลงกลับเป็นเซสชันได้ในภายหลัง (ดูข้อมูลเพิ่มเติมในเอกสาร SESSION_SERIALIZER) จากนั้นจึงแฮชข้อมูลที่ทำให้เป็นอนุกรมและใช้แฮชนี้ในภายหลังเป็นลายเซ็นเพื่อตรวจสอบความสมบูรณ์ของข้อมูลเซสชัน สุดท้ายจะส่งคืนคู่ข้อมูลนั้นให้กับผู้ใช้เป็นสตริงที่เข้ารหัส Base64
โดยวิธีการ: ก่อนเวอร์ชัน 1.6 Django เริ่มต้นที่จะใช้ pickle สำหรับการจัดลำดับข้อมูลเซสชัน เนื่องจาก ข้อกังวลด้านความปลอดภัย ตอนนี้วิธีการทำให้เป็นอนุกรมเริ่มต้นคือ django.contrib.sessions.serializers.JSONSerializer
วิธีใช้บริการระบุตำแหน่งของ Google
มาดูขั้นตอนการจัดการเซสชันที่ใช้งานได้จริง ที่นี่พจนานุกรมเซสชันของเราจะเป็นจำนวนเต็มและจำนวนเต็ม แต่คุณสามารถจินตนาการได้ว่าสิ่งนี้จะนำไปสู่เซสชันผู้ใช้ที่ซับซ้อนมากขึ้นได้อย่างไร
>>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{'count':1}'
ผลลัพธ์ของวิธีการจัดเก็บ (u’ZmUxOTY … == ’) คือสตริงที่เข้ารหัสที่มีเซสชันผู้ใช้ที่ต่อเนื่องกัน และ แฮชของมัน เมื่อเราถอดรหัสเราจะได้รับทั้งแฮช ('fe1964e ... ') และเซสชัน ({'count':1}
) กลับคืนมา
โปรดทราบว่าวิธีการถอดรหัสจะตรวจสอบเพื่อให้แน่ใจว่าแฮชถูกต้องสำหรับเซสชันนั้นรับประกันความสมบูรณ์ของข้อมูลเมื่อเราไปใช้ใน Flask ในกรณีของเราเราไม่กังวลมากเกินไปว่าเซสชันของเราจะถูกแทรกแซงจากฝั่งไคลเอ็นต์เนื่องจาก:
เราไม่ได้ใช้เซสชันที่อิงกับคุกกี้กล่าวคือเราไม่ได้ส่ง ทั้งหมด ข้อมูลผู้ใช้ไปยังไคลเอนต์
ใน Flask เราจำเป็นต้องอ่านอย่างเดียว SessionStore
ซึ่งจะแจ้งให้เราทราบว่ามีคีย์ที่ระบุอยู่หรือไม่และส่งคืนข้อมูลที่เก็บไว้
วิธีดาต้าเหมือง twitter
ต่อไปเรามาสร้างเวอร์ชันที่เรียบง่ายของเครื่องมือเซสชัน Redis (ฐานข้อมูล) เพื่อทำงานกับ Flask เราจะใช้ SessionStore
(กำหนดไว้ด้านบน) เป็นคลาสพื้นฐาน แต่เราจำเป็นต้องลบฟังก์ชันบางอย่างออกเช่นตรวจสอบลายเซ็นที่ไม่ดีหรือแก้ไขเซสชัน เราสนใจอ่านอย่างเดียว SessionStore
มากกว่า ที่จะโหลดข้อมูลเซสชันที่บันทึกจาก Django มาดูกันว่ามันมาพร้อมกันได้อย่างไร:
class SessionStore(object): # The default serializer, for now def __init__(self, conn, session_key, secret, serializer=None): self._conn = conn self.session_key = session_key self._secret = secret self.serializer = serializer or JSONSerializer def load(self): session_data = self._conn.get(self.session_key) if not session_data is None: return self._decode(session_data) else: return {} def exists(self, session_key): return self._conn.exists(session_key) def _decode(self, session_data): ''' Decodes the Django session :param session_data: :return: decoded data ''' encoded_data = base64.b64decode(force_bytes(session_data)) try: # Could produce ValueError if there is no ':' hash, serialized = encoded_data.split(b':', 1) # In the Django version of that they check for corrupted data # I don't find it useful, so I'm removing it return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions. If any of # these happen, return an empty dictionary (i.e., empty session). return {}
เราต้องการเพียง load
วิธีการเนื่องจากเป็นการใช้งานพื้นที่เก็บข้อมูลแบบอ่านอย่างเดียว นั่นหมายความว่าคุณไม่สามารถออกจากระบบจาก Flask ได้โดยตรง คุณอาจต้องการเปลี่ยนเส้นทางงานนี้ไปยัง Django แทน อย่าลืมว่าเป้าหมายคือการจัดการเซสชันระหว่างกรอบงาน Python ทั้งสองนี้เพื่อให้คุณมีความยืดหยุ่นมากขึ้น
ไมโครเฟรมเวิร์กของ Flask รองรับเซสชันที่ใช้คุกกี้ซึ่งหมายความว่าข้อมูลเซสชันทั้งหมดจะถูกส่งไปยังไคลเอนต์โดยเข้ารหัส Base64 และลงนามด้วยการเข้ารหัส แต่จริงๆแล้วเราไม่ค่อยสนใจการสนับสนุนเซสชันของ Flask มากนัก
สิ่งที่เราต้องการคือรับรหัสเซสชันที่ Django สร้างขึ้นและตรวจสอบกับส่วนหลังของ Redis เพื่อให้เรามั่นใจได้ว่าคำขอเป็นของผู้ใช้ที่ลงนามล่วงหน้า โดยสรุปกระบวนการที่ดีที่สุดคือ (สิ่งนี้จะซิงค์กับแผนภาพด้านบน):
การมีมัณฑนากรเพื่อตรวจสอบข้อมูลนั้นและตั้งค่า user_id
ปัจจุบันจะเป็นประโยชน์ ลงใน g
ตัวแปรในขวด:
from functools import wraps from flask import g, request, redirect, url_for def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): djsession_id = request.cookies.get('sessionid') if djsession_id is None: return redirect('/') key = get_session_prefixed(djsession_id) session_store = SessionStore(redis_conn, key) auth = session_store.load() if not auth: return redirect('/') g.user_id = str(auth.get('_auth_user_id')) return f(*args, **kwargs) return decorated_function
ในตัวอย่างด้านบนเรายังคงใช้ SessionStore
เรากำหนดไว้ก่อนหน้านี้เพื่อดึงข้อมูล Django จาก Redis หากเซสชันมี _auth_user_id
เราจะส่งคืนเนื้อหาจากฟังก์ชันมุมมอง มิฉะนั้นผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบเช่นเดียวกับที่เราต้องการ
ในการแบ่งปันคุกกี้ฉันพบว่าสะดวกในการเริ่ม Django และ Flask ผ่านไฟล์ WSGI เซิร์ฟเวอร์และกาวเข้าด้วยกัน ในตัวอย่างนี้ฉันได้ใช้ CherryPy :
from app import app from django.core.wsgi import get_wsgi_application application = get_wsgi_application() d = wsgiserver.WSGIPathInfoDispatcher({ '/':application, '/backend':app }) server = wsgiserver.CherryPyWSGIServer(('127.0.0.1', 8080), d)
ด้วยเหตุนี้ Django จะแสดงบน“ /” และ Flask จะแสดงบนปลายทาง“ / แบ็กเอนด์”
แทนที่จะตรวจสอบ Django เทียบกับ Flask หรือสนับสนุนให้คุณเรียนรู้ไมโครเฟรมเวิร์คของ Flask เท่านั้นฉันได้เชื่อม Django และ Flask เข้าด้วยกันเพื่อให้พวกเขาแบ่งปันข้อมูลเซสชันเดียวกันสำหรับการตรวจสอบสิทธิ์โดยมอบหมายงานให้ Django เนื่องจาก Django มาพร้อมกับโมดูลมากมายเพื่อแก้ปัญหาการลงทะเบียนผู้ใช้การเข้าสู่ระบบและการออกจากระบบ (เพื่อบอกชื่อไม่กี่อย่าง) การรวมสองเฟรมเวิร์กนี้จะช่วยให้คุณประหยัดเวลาอันมีค่าในขณะที่ให้โอกาสคุณในการแฮ็กไมโครเฟรมเวิร์คที่จัดการได้เช่น Flask