浅入浅出Flask框架:处理客户端通过POST方法传送的数据

renne006 10年前
   <p>作为一种HTTP请求方法,POST用于向指定的资源提交要被处理的数据。我们在某网站注册用户、写文章等时候,需要将数据保存在服务器中,这是一般使用POST方法。</p>    <p>本文使用python的requests库模拟客户端。</p>    <h2>建立Flask项目</h2>    <p>按照以下命令建立Flask项目HelloWorld:</p>    <pre>  <code class="language-python">mkdir HelloWorld  mkdir HelloWorld/static  mkdir HelloWorld/templates  touch HelloWorld/index.py  </code></pre>    <h2>简单的POST</h2>    <p>以用户注册为例子,我们需要向服务器<code>/register</code>传送用户名<code>name</code>和密码<code>password</code>。如下编写<code>HelloWorld/index.py</code>。</p>    <pre>  <code class="language-python">from flask import Flask, request    app = Flask(__name__)    @app.route('/')  def hello_world():      return 'hello world'    @app.route('/register', methods=['POST'])  def register():      print request.headers      print request.form      print request.form['name']      print request.form.get('name')      print request.form.getlist('name')      print request.form.get('nickname', default='little apple')      return 'welcome'    if __name__ == '__main__':      app.run(debug=True)  </code></pre>    <p><code>@app.route('/register', methods=['POST'])</code>是指url<code>/register</code>只接受POST方法。也可以根据需要修改<code>methods</code>参数,例如</p>    <pre>  <code class="language-python">@app.route('/register', methods=['GET', 'POST'])  # 接受GET和POST方法  </code></pre>    <p>具体请参考<a href="/misc/goto?guid=4959722754829684171" rel="external">http-methods</a>。</p>    <p>客户端<code>client.py</code>内容如下:</p>    <pre>  <code class="language-python">import requests    user_info = {'name': 'letian', 'password': '123'}  r = requests.post("http://127.0.0.1:5000/register", data=user_info)    print r.text  </code></pre>    <p>运行<code>HelloWorld/index.py</code>,然后运行<code>client.py</code>。<code>client.py</code>将输出:</p>    <pre>  <code class="language-python">welcome  </code></pre>    <p>而<code>HelloWorld/index.py</code>在终端中输出以下调试信息(通过<code>print</code>输出):</p>    <pre>  <code class="language-python">Content-Length: 24  User-Agent: python-requests/2.2.1 CPython/2.7.6 Windows/8  Host: 127.0.0.1:5000  Accept: */*  Content-Type: application/x-www-form-urlencoded  Accept-Encoding: gzip, deflate, compress      ImmutableMultiDict([('password', u'123'), ('name', u'letian')])  letian  letian  [u'letian']  little apple  </code></pre>    <p>前6行是client.py生成的HTTP请求头,由于<code>print request.headers</code>输出。</p>    <p><code>print request.form</code>的结果是:</p>    <pre>  <code class="language-python">ImmutableMultiDict([('password', u'123'), ('name', u'letian')])  </code></pre>    <p>这是一个<code>ImmutableMultiDict</code>对象。关于<code>request.form</code>,更多内容请参考<a href="/misc/goto?guid=4959722754918962072" rel="external">flask.Request.form</a>。关于<code>ImmutableMultiDict</code>,更多内容请参考<a href="/misc/goto?guid=4959722755001335690" rel="external">werkzeug.datastructures.MultiDict</a>。</p>    <p><code>request.form['name']</code>和<code>request.form.get('name')</code>都可以获取<code>name</code>对应的值。对于<code>request.form.get()</code>可以为参数<code>default</code>指定值以作为默认值。所以:</p>    <pre>  <code class="language-python">print request.form.get('nickname', default='little apple')  </code></pre>    <p>输出的是默认值</p>    <pre>  <code class="language-python">little apple  </code></pre>    <p>如果<code>name</code>有多个值,可以使用<code>request.form.getlist('name')</code>,该方法将返回一个列表。我们将client.py改一下:</p>    <pre>  <code class="language-python">import requests    user_info = {'name': ['letian', 'letian2'], 'password': '123'}  r = requests.post("http://127.0.0.1:5000/register", data=user_info)    print r.text  </code></pre>    <p>此时运行<code>client.py</code>,<code>print request.form.getlist('name')</code>将输出:</p>    <pre>  <code class="language-python">[u'letian', u'letian2']  </code></pre>    <h2>上传文件</h2>    <p>这一部分的代码参考自<a href="/misc/goto?guid=4959722755077454733" rel="external">How to upload a file to the server in Flask</a>。</p>    <p>假设将上传的图片只允许’png’、’jpg’、’jpeg’、’git’这四种格式,通过url<code>/upload</code>使用POST上传,上传的图片存放在服务器端的<code>static/uploads</code>目录下。</p>    <p>首先在项目<code>HelloWorld</code>中创建目录<code>static/uploads</code>:</p>    <pre>  <code class="language-python">$ mkdir HelloWorld/static/uploads  </code></pre>    <p><code>werkzeug</code>库可以判断文件名是否安全,例如防止文件名是<code>../../../a.png</code>,安装这个库:</p>    <pre>  <code class="language-python">$ pip install werkzeug  </code></pre>    <p>修改<code>HelloWorld/index.py</code>:</p>    <pre>  <code class="language-python">from flask import Flask, request  from werkzeug.utils import secure_filename  import os    app = Flask(__name__)  app.config['UPLOAD_FOLDER'] = 'static/uploads/'  app.config['ALLOWED_EXTENSIONS'] = set(['png', 'jpg', 'jpeg', 'gif'])    # For a given file, return whether it's an allowed type or not  def allowed_file(filename):      return '.' in filename and \             filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']    @app.route('/')  def hello_world():      return 'hello world'    @app.route('/upload', methods=['POST'])  def upload():      upload_file = request.files['image01']      if upload_file and allowed_file(upload_file.filename):          filename = secure_filename(upload_file.filename)          upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))          return 'hello, '+request.form.get('name', 'little apple')+'. success'      else:          return 'hello, '+request.form.get('name', 'little apple')+'. failed'    if __name__ == '__main__':      app.run(debug=True)  </code></pre>    <p><code>app.config</code>中的config是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数<code>allowed_file(filename)</code>用来判断<code>filename</code>是否有后缀以及后缀是否在<code>app.config['ALLOWED_EXTENSIONS']</code>中。</p>    <p>客户端上传的图片必须以<code>image01</code>标识。<code>upload_file</code>是上传文件对应的对象。<code>app.root_path</code>获取<code>index.py</code>所在目录在文件系统中的绝对路径。<code>upload_file.save(path)</code>用来将<code>upload_file</code>保存在服务器的文件系统中,参数最好是绝对路径,否则会报错(网上很多代码都是使用相对路径,但是笔者在使用相对路径时总是报错,说找不到路径)。函数<code>os.path.join()</code>用来将使用合适的路径分隔符将路径组合起来。</p>    <p>好了,定制客户端<code>client.py</code>:</p>    <pre>  <code class="language-python">import requests    files = {'image01': open('01.jpg', 'rb')}  user_info = {'name': 'letian'}  r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=files)    print r.text  </code></pre>    <p>当前目录下的<code>01.jpg</code>将上传到服务器。运行<code>client.py</code>,结果如下:</p>    <pre>  <code class="language-python">hello, letian. success  </code></pre>    <p>然后,我们可以在<code>static/uploads</code>中看到文件<code>01.jpg</code>。</p>    <p>要控制上产文件的大小,可以设置请求实体的大小,例如:</p>    <pre>  <code class="language-python">app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB  </code></pre>    <p>不过,在处理上传文件时候,需要使用<code>try:...except:...</code>。</p>    <p>如果要获取上传文件的内容可以:</p>    <pre>  <code class="language-python">file_content = request.files['image01'].stream.read()  </code></pre>    <h2>处理JSON</h2>    <p>处理JSON时,要把请求头和响应头的<code>Content-Type</code>设置为<code>application/json</code>。</p>    <p>修改<code>HelloWorld/index.py</code>:</p>    <pre>  <code class="language-python">from flask import Flask, request, Response  import json    app = Flask(__name__)    @app.route('/')  def hello_world():      return 'hello world'    @app.route('/json', methods=['POST'])  def my_json():      print request.headers      print request.json      rt = {'info':'hello '+request.json['name']}      return Response(json.dumps(rt),  mimetype='application/json')    if __name__ == '__main__':      app.run(debug=True)  </code></pre>    <p>修改后运行。</p>    <p>修改<code>client.py</code>:</p>    <pre>  <code class="language-python">import requests, json    user_info = {'name': 'letian'}  headers = {'content-type': 'application/json'}  r = requests.post("http://127.0.0.1:5000/json", data=json.dumps(user_info), headers=headers)  print r.headers  print r.json()  </code></pre>    <p>运行<code>client.py</code>,将显示:</p>    <pre>  <code class="language-python">CaseInsensitiveDict({'date': 'Tue, 24 Jun 2014 12:10:51 GMT', 'content-length': '24', 'content-type': 'application/json', 'server': 'Werkzeug/0.9.6 Python/2.7.6'})  {u'info': u'hello letian'}  </code></pre>    <p>而<code>HelloWorld/index.py</code>的调试信息为:</p>    <pre>  <code class="language-python">Content-Length: 18  User-Agent: python-requests/2.2.1 CPython/2.7.6 Windows/8  Host: 127.0.0.1:5000  Accept: */*  Content-Type: application/json  Accept-Encoding: gzip, deflate, compress      {u'name': u'letian'}  </code></pre>    <p>这个比较简单,就不多说了。另外,如果需要响应头具有更好的可定制性,可以如下修改<code>my_json()</code>函数:</p>    <pre>  <code class="language-python">@app.route('/json', methods=['POST'])  def my_json():      print request.headers      print request.json      rt = {'info':'hello '+request.json['name']}      response = Response(json.dumps(rt),  mimetype='application/json')      response.headers.add('Server', 'python flask')      return response</code></pre>    <p> </p>    <p>来自:http://www.letiantian.me/2014-06-24-flask-process-post-data/</p>