背景
做app开发的,和后台连调是一个很重要的工作。在我来到这家公司之前,一只以为所有的功能是后台先开发出来然后app才开始开发。但是由于现在app开发和后台开发是两套人马,所以不可避免的需要同时开发甚至同时上线。我们不得不面临的一个问题是后台只给出了接口,客户端并没有可以连调的环境,这样就不得不自己写虚拟数据模拟。让客户端能够按某个流程能够点下去。
解决方法
- 在我接手钱盒的时候,很多地方有一个全局静态flag,好像叫一个走马观花模式。开启这个flag,可以不与后台交互,而直接一步一步走界面。这种方法固然比较好,但是这个修改了代码的流程,在真实环境下用不到这个逻辑,而且容易给app测试埋坑。
- 利用mock技术模拟http返回,这虽然有很多框架可以做到,但是对于新人而言,学习成本较高,不同平台可能有不同的解决方案。因此也不是解决连调问题的首选。
- 写http服务模拟数据返回。这是一种兼容多个平台的方案,android ios 以及h5 都能够适配的通用解决方案。下面分析一下如何实现
实现
做过servlet 的人都知道,servlet是一个基于java的http服务器的拓展,类似于cgi(common gateway interface)。一个servlet实现类需要一般来说要实现doGet doPost,用来处理客户端发送过来的数据。处理完成之后通过response返回数据。这样我们就可以动态的给客户端返回数据了。但是servlet程序是需要部署到server容器里面。比如tomcat才能执行,要装java运行环境。而且java代码是相对比较繁琐。因此我们用另外一种可以飞起来的语言python来实现。只需要python环境即可,python标准环境就包含了http相关的类库
我们将返回的数据写死在代码里面显然是不科学的。因为不可能我们要改一个返回就得修改代码,并重新运行,采用文件保存返回内容是一个很好的解决方法,然后通过不同的请求路径去获取相应的文件。没有找到相应文件就创建相应文件。
于是开始网上查询 https://wiki.python.org/moin/BaseHttpServer 只需实现BaseHttpServer就能实现一个简单的http请求处理程序。
import time
import BaseHTTPServer
HOST_NAME = 'example.net' # !!!REMEMBER TO CHANGE THIS!!!
PORT_NUMBER = 80 # Maybe set this to 9000.
class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_HEAD(s):
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
def do_GET(s):
"""Respond to a GET request."""
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
s.wfile.write("<html><head><title>Title goes here.</title></head>")
s.wfile.write("<body><p>This is a test.</p>")
# If someone went to "http://something.somewhere.net/foo/bar/",
# then s.path equals "/foo/bar/".
s.wfile.write("<p>You accessed path: %s</p>" % s.path)
s.wfile.write("</body></html>")
if __name__ == '__main__':
server_class = BaseHTTPServer.HTTPServer
httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
print time.asctime(), "Server Stops - %s:%s" % (HOST_NAME, PORT_NUMBER)
整个代码就35行。在此基础上参阅了一些相关文档,我重新写了do_POST方法,可以创建和读取文件
一个简单的模拟返回app 就搭建好了 代码如下
#!/usr/bin/python
# coding=utf-8
"""
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
"""
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os
import cgi
import time
import codecs
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
print sys.getdefaultencoding()
PORT_NUMBER = 8888
RES_FILE_DIR = "."
class myHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.path = "/index.html"
try:
# 根据请求的文件扩展名,设置正确的mime类型
sendReply = False
if self.path.endswith(".html"):
mimetype = 'text/html'
sendReply = True
if self.path.endswith(".jpg"):
mimetype = 'image/jpg'
sendReply = True
if self.path.endswith(".gif"):
mimetype = 'image/gif'
sendReply = True
if self.path.endswith(".js"):
mimetype = 'application/javascript'
sendReply = True
if self.path.endswith(".css"):
mimetype = 'text/css'
sendReply = True
if sendReply == True:
# 读取相应的静态资源文件,并发送它
f = open(os.curdir + os.sep + self.path, 'rb')
self.send_response(200)
self.send_header('Content-type', mimetype)
self.end_headers()
self.wfile.write(f.read())
f.close()
return
except IOError:
self.send_error(404, 'File Not Found: %s' % self.path)
def do_POST(self):
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers['Content-Type'],
})
print self.path
keys = form.keys()
params = ""
for key in keys:
params += key + "=" + form.getvalue(key, "") + "\n"
print params[:len(params) - 1]
# 读取相应的静态资源文件,并发送它
full_file_path = os.curdir + os.sep + self.path
if os.path.isfile(full_file_path):
file_out = open(full_file_path, 'rb')
full_text = file_out.read()
file_out.close()
self.send_response(200)
self.end_headers()
if len(full_text) > 0:
self.wfile.write(full_text)
print(full_text)
else:
self.wfile.write(u"{\"status\":1,\"remark\":\"请求处理不存在\"}")
else:
self.send_response(200)
self.end_headers()
retstr = u"{\"status\":-1,\"remark\":\"请求处理不存在,新建处理\"}"
self.wfile.write(retstr)
if not os.path.exists(os.path.split(full_file_path)[0]):
os.makedirs(os.path.split(full_file_path)[0])
fwrite = codecs.open(full_file_path, 'wb', 'utf-8')
fwrite.write(retstr)
fwrite.close()
print retstr, "已保存到文件,路径:", full_file_path
try:
server = HTTPServer(('', PORT_NUMBER), myHandler)
print 'Started httpserver on port ', PORT_NUMBER
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down the web server'
server.socket.close()