PHP & Django(Python)의 통합 및 세션 공유


코드윙즈 사이트를 개발하면서, 자동 채점 시스템을 구현하다가 php로 만들어진 hustoj와 Django로 만들어진 기존 서비스를 합쳐야 하는 상황이 되었다.

우리가 해야할 것은 php로 만들어진 기존의 사이트와, django[python]으로 만들어진 사이트 두개를 한 url 내에서 서비스 하며, django단에서 로그인 할 경우, php에서도 로그인이 되도록 수정하는 것이 목표이다.

바쁜 사람들을 위해 진행 단계를 요약하면 아래와 같다.

  1. nginx를 이용하여 접근 url별로 다른 sock에게 전달 [php파일은 php5-fpm.sock이, 그 외의 것은 tornado_server에게 전달]
  2. php와 django서버가 같은 database를 참조하도록 만들기
  3. django의 session data 저장 방식을 php style로 변경[phpserializer를 이용하여, session engine에서 encode할 때 형태를 변경]
  4. php단에서 session_handler를 파일 참조에서 database의 django_session table을 참조하도록 수정
  5. php.ini 파일에서 session_serializer를 php_serializer로 수정
  6. django에서 request.session[“키”] = 값; 을 등록 한 후, php에서 session_start()호출 후 $_SESSION[“키”]값을 조사하기.

먼저 nginx를 이용하여, 접근하는 url에 따라 채점 관련 서비스는 php5-fpm sock에게, 나머지는 기존의 tornado 서버로 전달한다.

image1

이제 php5 파일은 로컬의 /var/www/html 경로에 있는 template들을, 그 외의 요청은 기존의 tornado 서버가 돌고 있는 도커로 전달한다. 물론 docker -v 옵션을 이용해여 /var/run/php5-fpm.sock을 nginx 내부 컨테이너와 루트의 것을 연결해줘야 동작한다.

가장 중요한 점은 둘의 세션 공유인데, 기본적으로 django와 php에서 사용하는 세션 핸들링 방식이 전혀 다르기에, django의 세션 저장 방식을 php에 맞게 바꿔야한다.

기존의 django_session 테이블을 보면

image2

위와 같은 형태인데, 세션 데이터를 보면 아래와 같이

MDg1OGQ1M2BBSIyMzhmOHHYThiNj1SSHEE1MGEwY2RhMGI0Z1235dXRoX3VzZXJfaWQiOiI0MCIsIl9hdXRoX3VzZXJfYmFja2VuZCI6ImRqYW5nby5jb2aHMEGguYmFja2VuZWHTWESEYW25212IiwiX2F1dFSXNlcl9oYXNoIjoiYjZmZGY0YmY3NDJhMDJlZjg2NTJlOWJmNjY3NTBmMTFmNDNlZWViZSJ9

키와 값들이 해쉬된 형태로 저장되어있다. 이것을 php에서 사용할 수 있는 데이터로 가공하기 위해서는 django의 Session Engine을 수정하여 한다.

비슷한 역할을 해주는 django_php_bridge 라는 플러그인이 있는데, 문제는 기존 플러그인이 2012년 이후로 더 이상 업데이트가 안되는 것인지,원형 그대로 받아서 실행하는 경우 string conversion 오류가 발생한다.

그러므로 pip로 설치하지 말고, git clone https://github.com/winhamwr/django-php-bridge/ 을 통해 파일을 받은 후, backends/db.py의 코드를 아래와 같이 수정한다.

import phpserialize
def BinaryToDict(targetstr):
    newDict = {}
    for keys in targetstr:
         newDict[keys.decode()] = targetstr[keys].decode()
    return newDict

from django.contrib.sessions.backends.db import SessionStore as DbStore

class SessionStore(DbStore):
    def __init__(self, session_key=None):
        # call the super class's init
        super(SessionStore, self).__init__(session_key)

    def decode(self, session_data):
        return BinaryToDict(phpserialize.loads(session_data.encode()))

    def encode(self, session_dict):
        return phpserialize.dumps(session_dict)
        

setup.py 또한 아래와 같이 수정해야 한다.

setup(
    name='django-php-bridge',
    version=django_php_bridge.__version__,
    description=django_php_bridge.__doc__,
    author=django_php_bridge.__author__,
    author_email=django_php_bridge.__contact__,
    url=django_php_bridge.__homepage__,
    long_description=long_description,
    packages=['django_php_bridge', 'django_php_bridge.backends],
    license='BSD',
    platforms=['any'],
    classifiers=CLASSIFIERS,
    install_requires=['phpserialize==1.3'],
)

이렇게 변경 한 후 python3 setup.py install을 하면 정상적으로 플러그인이 설치된다. 플러그인을 설치 한 후, settings.py에 아래의 두줄을 추가한다. SESSION_ENGINE = ‘django_php_bridge.backends.db’ SESSION_COOKIE_NAME = ‘PHPSESSID’ 위의 절차를 모두 마친 후, 장고에서 로그인 하면 기존의 한줄짜리 세션데이터가, a:4{s:13:”_auth_user_id”;s:2:”10”;s:7:”user_id”;s:2:”bjPark”;s:18:”_auth_user_backend”;s:41:”django.contrib.auth.backends.ModelBackend”;s:15:”_auth_user_hash”;s:40:”b6fdf4bf7sadlksajdlksaj”;} 와 같은 형태로 바뀌게 된다.

이제 php단으로 넘어가서, /etc/php5/fpm/php.ini 파일을 연 후, php.ini의

session.save_path = files -> user로 수정 session.serialize_handler = php -> php_serialize로 수정한다.

그후 php에서 세션 데이터를 처리하는 session_handler 함수를 기존의 파일 저장 방식에서, Database Storage 방식으로 바꾸면 된다.

<?php

define('TWO_WEEKS', 1209600);
class SysSession implements SessionHandlerInterface
{
    private $link;

    public function open($savePath, $sessionName)
    {
        $link = mysqli_connect("dbhost","root","password","databasename");
        if($link){
            $this->link = $link;
            return true;
        }else{
            return false;
        }
    }
    public function close()
    {
        mysqli_close($this->link);
        return true;
    }
    public function read($session_key)
    {

       $link = mysqli_connect("dbhost","root","password","databasename");
        if($link){
            $this->link = $link;
        }else{

        }

       $session_key = mysqli_real_escape_string($link, $session_key);

        $query = "SELECT session_data
        FROM django_session
        WHERE session_key = '$session_key'";


        if( $result = mysqli_query($this->link,$query)){
            if(mysqli_num_rows($result) > 0){

                $record = mysqli_fetch_assoc($result);
                return $record['session_data'];
            }else{
                return '';
            }
        }else{
            return '';
        }


    }
    
    public function write($session_key, $session_data)
    {
        $link = mysqli_connect("dbhost","root","password","databasename");
        if($link){
            $this->link = $link;
        }else{

        }

        $expire_date = time() + TWO_WEEKS;
        $expire_date = date('Y-m-d H:i:s', $expire_date);
        $session_key = mysqli_real_escape_string($link, $session_key);


        $expire_date = mysqli_real_escape_string($link,$expire_date);
        $session_data = mysqli_real_escape_string($link,$session_data);



        $query = "SELECT session_key
        FROM django_session
        WHERE session_key = '$session_key'";

        $result = mysqli_query($this->link,$query);

        $num = mysqli_num_rows($result);

        if($num ==1){
            $sql = "UPDATE django_session
            SET session_data = '$session_data',
            expire_date = '$expire_date'
            WHERE session_key = '$session_key'";
        }else{
            $sql = "INSERT INTO django_session(session_key, session_data, expire_date)
            VALUES('$session_key', '$session_data', '$expire_date')";
        }
        return mysqli_query($this->link, $sql);


    }
    public function destroy($id)
    {
        $link = mysqli_connect("dbhost","root","password","databasename");
        if($link){
            $this->link = $link;
        }else{

        }

        $id = mysqli_real_escape_string($link, $id);
        $result = mysqli_query($this->link,"DELETE FROM django_session WHERE session_key ='".$id."'");
        if($result){
            return true;
        }else{
            return false;
        }
    }
    public function gc($maxlifetime)
    {
        $result = mysqli_query($this->link,"DELETE FROM django_session WHERE ((UNIX_TIMESTAMP(expire_date) + ".$maxlifetime.") < ".$maxlifetime.")");
        if($result){
            return true;
        }else{
            return false;
        }
    }
}
$handler = new SysSession();
session_set_save_handler($handler, true);
?>

대충 위와 같은데, mysqli_connect부분에 아이디, 비밀번호, 호스트명 등을 적절하게 바꿔주면 된다. 그후 맨 밑에 session_set_save_handler를 통해 우리가 만든 세션 핸들러를 등록해주면 된다.

이제 session_start() 함수를 호출 할 경우, $_SESSION 변수를 통해, 장고 웹 사이트에서 request.session[“키”] = 값으로 등록한 값들을 php단에서 $_SESSION[“키”]를 통해 사용할 수 있다.

Back to blog