Search code examples
requestwebservercherrypy

Request.Post Method to Web Server using CherryPy


I am following the blog: Build your own Python RESTful Web Service to mock a web server using cherrypy.

The server code is

import cherrypy

class MyWebService(object):
   @cherrypy.expose
   @cherrypy.tools.json_out()
   @cherrypy.tools.json_in()
   def process(self):
      return "hello world!"

if __name__ == '__main__':
    config = {'server.socket_host': '0.0.0.0'}
    cherrypy.config.update(config)
    cherrypy.quickstart(MyWebService())

running above script by python server.py will start a service at http://localhost:8080.

Successful Call

Then we can call the service using post method:

import requests
headers = {'Content-Type': 'application/json'}
response = requests.post('http://localhost:8080/process', headers=headers, json={})

It successfully returns "hello world!" with status= 200.

Failed Call

However, if changing "Content-Type": application/json -> text/plain in headers and json -> data:

headers = {'Content-Type': 'text/plain'}
response = requests.post('http://localhost:8080/process', headers=headers, data={})

It responds the error code 415, and the error message

Traceback (most recent call last):
  File "/Users/hshung/opt/anaconda3/lib/python3.9/site-packages/requests/models.py", line 910, in json
    return complexjson.loads(self.text, **kwargs)
  File "/Users/hshung/opt/anaconda3/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/Users/hshung/opt/anaconda3/lib/python3.9/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/Users/hshung/opt/anaconda3/lib/python3.9/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

.....

I did try the following server code

@cherrypy.tools.json_in(content_type=['application/json', 'text/plain'])

The API call still fails, and the status code is 400.

Can anyone who is familiar with cherryPy and API request help me figure out how to fix it?


Solution

  • The problem is that with:

    headers = {'Content-Type': 'text/plain'}
    response = requests.post('http://localhost:8080/process', headers=headers, data={})
    

    You're sending a POST request with an empty body, it should work if you specify the data param as a string containing any valid json:

    headers = {'Content-Type': 'text/plain'}
    response = requests.post('http://localhost:8080/process', headers=headers, data='{}')
    

    For reference take a look a the values that are sent:

    >>> requests.post('http://localhost:8080/process', headers=headers, json={})
    DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost:8080
    send: b'POST /process HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\n'
    send: b'{}'
    reply: 'HTTP/1.1 200 OK\r\n'
    header: Content-Type: application/json
    header: Server: CherryPy/18.6.1
    header: Date: Fri, 22 Jul 2022 23:16:49 GMT
    header: Content-Length: 14
    DEBUG:urllib3.connectionpool:http://localhost:8080 "POST /process HTTP/1.1" 200 14
    <Response [200]>
    
    >>> requests.post('http://localhost:8080/process', headers=headers, data={})
    DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost:8080
    send: b'POST /process HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n'
    reply: 'HTTP/1.1 400 Bad Request\r\n'
    header: Content-Type: text/html;charset=utf-8
    header: Server: CherryPy/18.6.1
    header: Date: Fri, 22 Jul 2022 23:16:55 GMT
    header: Content-Length: 3023
    DEBUG:urllib3.connectionpool:http://localhost:8080 "POST /process HTTP/1.1" 400 3023
    <Response [400]>
    
    >>> requests.post('http://localhost:8080/process', headers=headers, data='{}')
    DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost:8080
    send: b'POST /process HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\n'
    send: b'{}'
    reply: 'HTTP/1.1 200 OK\r\n'
    header: Content-Type: application/json
    header: Server: CherryPy/18.6.1
    header: Date: Fri, 22 Jul 2022 23:17:00 GMT
    header: Content-Length: 14
    DEBUG:urllib3.connectionpool:http://localhost:8080 "POST /process HTTP/1.1" 200 14
    <Response [200]>
    >>> 
    

    And you're correct with:

    @cherrypy.tools.json_in(content_type=['application/json', 'text/plain'])
    

    That is also required.