linux+apache+mod_python+wechat_sdk搭建微信公共賬號服務器


linux+apache+mod_python+wechat_sdk搭建微信公共賬號服務器

轉載請注明本文原作者:FignerLiu

PRE

最近嘗試了下使用python搭建微信公共賬號服務器,實現了簡單的消息收發功能。其中遇到了很多問題,特此記錄下來。

服務器的選擇

如果使用python做開發語言,一般選用以下幾種服務器可以用來做微信公共賬號服務器(如果不全,歡迎大家補充):

  • SAE + wsgi
  • apache + mod_python
  • apache + mod_wsgi
  • nginx + wsgi

如果對使用獨立的服務器沒有需求,或者對apache和nginx的配置不熟悉,一般采用SAE + wsgi做服務器。由於手頭正好有閑置的Apache,本着生命在於折騰,從折騰中學習的目的,我選擇了apache + mod_python作為我的服務器。以下的內容均以此為背景。
在后面的開發過程中我體會到,我的這個選擇絕對是最折騰人的 = 。=

sdk的選擇

除了微信官方的sdk外,github上找到兩個第三方sdk:

  • wechat-python-sdk
  • WeRoBot

這兩個sdk各有優劣,wechat-python-sdk代碼更簡潔易懂,而WeRoBot功能更全,包含微信支付API操作類等wechat-python-sdk不具有的功能。本次我選擇使用了wechat-python-sdk

服務器搭建與配置

首先操作系統我選擇的是放在阿里雲上的centos 5.10
需要安裝如下軟件包:

  • apache 我選用的是httpd-2.4.10
  • expat expat是用來解析xml等文件的庫,我選用的是expat-2.0.1.tar.gz
  • apr Apache Portable Runtime我選用的是apr-1.5.1.tar.gz
  • apr-util 我選用的是apr-util-1.5.4.tar.gz
  • prce Perl Compatible Regular Expressions 我選用的是pcre-8.36.tar.gz 。 上述expat,apr,apr-util,prce均為安裝Apache時的依賴項,注意各軟件包的版本,如果不一致可能導致不兼容的問題。
  • mod_python Apache處理python的模塊,我選用的是mod_python-3.5.0.tgz
  • python-devel mod_python的安裝依賴項,我選用的是python-devel-2.6.6-52.el6.x86_64,應和系統使用的python版本一致。
  • python-pip python庫安裝工具,用來安裝wechat-sdk
  • wechat-sdk 微信平台第三方sdk
  • virtualenv(可選) virtualenv的簡介

    首先安裝各軟件包

    為了統一和方便,我把所有軟件包都安裝到了/usr/local下,均為編譯安裝。
    以下是部分安裝配置命令
    #pcre
    ./configure --prefix=/usr/local/pcre
    #apr
    ./configure --prefix=/usr/local/apr
    #apr-util
    ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr --with-expat=/usr/local/expat/
    #apache
    ./configure --prefix=/usr/local/apache --with-expat=/usr/local/epxat --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util --with-pcre=/usr/local/pcre/
    或者
    ./configure --prefix=/usr/local/apache --with-expat=/usr/local/epxat --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util --with-pcre=/usr/local/pcre/
    #mod_python
    ./configure --prefix=/usr/local/mod_python --with-apxs=/usr/local/apache/bin/apxs

 

除wechat-sdk外,安裝命令均為make && make install。wechat-sdk安裝方法為pip install wechat-sdk

配置apache

首先將Apache配置為自啟動服務,可參考這篇文章
服務啟動成功后,在瀏覽器輸入你的服務器ip,可看到如下It works! 字樣,則表示Apache安裝成功。接下來修改Apache配置文件,打開文件/usr/local/apache/httpd.conf,分別在對應位置添加如下配置
<Directory "/usr/local/apache/htdocs">下面添加

<Directory "/usr/local/apache/htdocs/daemon/webchat">
    SetHandler mod_python
    PythonHandler index
    PythonDebug On
    Order allow,deny
    Allow from all
</Directory>

 

<IfModule alias_module>下面添加

Alias /mywechat "/usr/local/apache/htdocs/daemon/webchat/mywechat.py" 

上面的配置的意思是:1.告訴Apache,目錄/usr/local/apache/htdocs/daemon/webchat將使用mod_python處理客戶端請求,所有請求交給這個目錄下的index.py來處理;2.告訴Apache,為路徑/usr/local/apache/htdocs/daemon/webchat/mywechat.py起了個別名,當客戶端訪問/mywechat的時候,實際上就是在訪問/usr/local/apache/htdocs/daemon/webchat
注,上述配置需要設置了DocumentRoot為/usr/local/apache/htdocs才有效

功能開發

接下來我們只需要向index.py和mywechat.py中添加對應的請求處理邏輯即可!
我們實現最簡單的功能,即接收服務器請求,並給客戶端發送消息。
要完成這一步,我們首先要了解我們都可能收到哪些請求,以及這些請求的作用。

  1. 首先在你登錄微信公共賬號,並點擊成為開發者后,系統會讓你驗證你的服務器配置,這時微信公共平台服務器會向你的系統發送一個GET請求,來驗證你的服務器,你需要按照微信制定的規則來返回驗證信息。
  2. 當驗證通過后,微信普通用戶向你發送消息時,普通用戶會發送給你的服務器一個POST請求,這個請求將請求內容包含在一個xml文檔中。
    簡單來說,你需要處理這兩種正常請求。此外,在你公共賬號投入商業使用之前,還需要能夠處理別人偽造的非法請求,從而避免信息被竊取。

示例代碼

index.py

#encoding:utf-8
from mod_python import apache
import os
def handler(req):
        handler = req.uri[req.uri.rfind('/')+1:]
        if handler[-3:] == ".py" :
                handler = handler[0:-3]
        if not handler == "index" :
                req.add_handler("PythonHandler", handler);
        else:
                req.write("using the default handler  : index")
        return apache.OK

 

mywechat.py

# -*- coding: utf-8 -*-
#!/usr/bin/python
from mod_python import apache
from mod_python import util
import os
from wechat_sdk import WechatBasic
import sys
import _apache
parse_qsl = _apache.parse_qsl

def handler(req):
        req.no_cache=True
        wechat = WechatBasic(token='yourtoken')
        if req.method == "GET":
                req.content_type = "text/plain"
                args=req.args
                apache.log_error("get req caught!!!")
                check(req,wechat)
        elif req.method == "POST":
                req.content_type = "text/xml"
                apache.log_error("post req caught!!!")
                parse(req,wechat)
        else:
                apache.log_error("unknown req method")
        return apache.OK

def check(req,wechat):
        parameters = util.FieldStorage(req)
        apache.log_error(str(parameters))
        signature = parameters.get('signature',None)
        timestamp = parameters.get('timestamp',None)
        nonce = parameters.get('nonce',None)
        echostr = parameters.get('echostr','')
        if wechat.check_signature(signature=signature, timestamp=timestamp, nonce=nonce):
                apache.log_error("check succeed")
                req.write(echostr)
                return apache.OK
        else:
                apache.log_error("check failed")
                return apache.HTTP_UNAUTHORIZED
def parse(req,wechat):
        #get request body
        try:
                clen = int(req.headers_in["content-length"])
        except (KeyError,ValueError):
                raise apache.SERVER_RETURN(apache.HTTP_LENGTH_REQUIRED)
        req_body = req.read(clen)
        apache.log_error(req_body)
        #parse body
        try:
                wechat.parse_data(req_body)
                message = wechat.get_message()
                response = None
                if message.type == 'text':
                        if message.content == 'wechat':
                                response = wechat.response_text('^_^')
                        else:
                                text=u'text'
                                response = wechat.response_text(text.encode('utf-8') + message.content.encode('utf-8'))
                elif message.type == 'image':
                        response = wechat.response_text('image')
                elif message.type == 'subscribe':
                        response = wechat.response_text('welcome to mywechat!!!')
                else:
                        response = wechat.response_text('unknown'+message.type)
                req.write(response)
                apache.log_error(response)
        except Exception as e:
                apache.log_error("\n\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\n")
                apache.log_error(str(e))
                apache.log_error("\n\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\n")

 

這樣簡單的demo就Ok了!
你可以拿起手機給自己的公共賬號發消息來測試了!

tips

  1. mod_python比較蛋疼的一個地方就是,很多時候出錯了它在log里只說一句:Segment Fault.....,根本就沒什么用,所以你需要在可能出現異常的地方將它們捕獲,並用apache.log_error(str(e))將錯誤信息輸出到log中。
  2. 實際安裝過程中,遇到了很多由於expat不兼容而出現的問題,而官方又沒有說明,所以我在這上面花了很多時間。實際上centos 5.10系統中預裝了expat,但配置Apache安裝項時,指定系統的expat的時候總會報錯,所以決定自己編譯安裝expat。mod_python使用過程中,會加載pyexpat.so,而我的系統中pyexpat.so僅支持版本為2.0.1的expat。由於pyexpat.so為python安裝包內的內容,我們無法更改,所以我們只能安裝版本為2.0.1的expat來解決這個問題。
  3. 使用這種方式實際使用時我還遇到了這個錯誤“symbol XML_SetHashSalt, version EXPAT_2_0_1_RH not defined in file libexpat.so.1 with link time reference”,通過strings libexpat.so.1.5.2 |grep expat -i我發現我安裝的expat中不包含“EXPAT_2_0_1_RH”這個字段,而系統自帶的則包括。
    解決方法是
    cd d /usr/local/expat/lib ln -s /lib64/libexpat.so.1.5.2 libexpat.so.1.5.2 
    這個解決方法很不漂亮,但是目前沒找到別的解決辦法。

useful links

  1. 微信公共平台開發者文檔
  2. 微信公眾平台 Python 開發包文檔
  3. modpython官方文檔
  4. python_publisher對post請求content-type的限制
  5. logging with modpython
  6. 解決expat版本兼容問題IssuesWithExpatLibrary

TODO

文字編碼問題還沒有解決,當前環境無法給用戶發送中文消息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM