Angular6 HttpClient POST 和 flask - httpclient 和 curl 请求之间后端的内容差异

Angular6 HttpClient POST and flask - content difference on backend between httpclient and curl request

我正在使用 HttpClient 做一个 post:

return this.http.post(`reporting/report/generate/${code}`, {'mimetype': mimetype, 'input': value}, {responseType: mimetype});

针对我的 API 服务器使用以下 mimetype:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,所以我正在获取 xlsx 数据。

在服务器端(通过 nginx 和 uwsgi),我 运行 flask 具有以下代码(裁剪):

    outputStream = io.BytesIO()

    workbook = xlsxwriter.Workbook(outputStream)
    worksheet = workbook.add_worksheet(Report.name())

    [...]

    worksheet.write(0, 0, 'YY', 'XX')

    workbook.close()

    output = outputStream.getvalue()
    outputStream.close()

    return Response(output, mimetype=mimetype)

(我的回复使用相同的 mimetype)。

回到 angular,我正在使用 blob url 等来下载我的文件,但这并不重要。 问题是当我从 angular 发出 Http 请求时,我的 Excel 文件被破坏了,当我使用 curl 执行完全相同的请求(从 Chrome 网络选项卡)。

但是:我在服务器端写了一个临时 xlsx 文件,两个文件都可以。

我打印了 output 的字节,我看到有些字节是不同的:

b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x13}\x15M\xf5\x8e\x0eH\xb9\x01\x00\x00I\x04\x00\x00\x18\...docProps/app.xmlPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\x13}\x15M\x9c\xed\xd2_%\x01\x00\x00P\x02\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00

对比

b'PK\x03\x04\x14\x00\x00\x00\x08\x003}\x15M\xf5\x8e\x0eH\xb9\x01\x00\x00I\x04\x00\x00\...docProps/app.xmlPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x003}\x15M\xa5\xc4\xa4$%\x01\x00\x00P\x02\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x81E\x06\x00\x00docProps/core.xmlPK\x01\x02\x14\x03\

90%的字节是相同的,但有些是不同的。

我找了好几个小时都没找到问题所在。 奇怪的是,临时文件在这两种情况下都可以,但表示显示略有不同。

我也分析了 Requests,我发现了一个小区别:

<Request 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1' [POST]>
{'environ': {'QUERY_STRING': ''
REQUEST_METHOD': 'POST'
CONTENT_TYPE': 'application/json'
CONTENT_LENGTH': '126'
REQUEST_URI': '/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1'
PATH_INFO': '/report/generate/DEMO_TECH_DEMO1'
DOCUMENT_ROOT': '/usr/share/nginx/html'
SERVER_PROTOCOL': 'HTTP/1.1'
REQUEST_SCHEME': 'http'
REMOTE_ADDR': '192.168.69.16'
REMOTE_PORT': '54436'
SERVER_PORT': '80'
SERVER_NAME': 'lsdev01'
HTTP_HOST': 'lsdev01'
HTTP_CONNECTION': 'keep-alive'
HTTP_CONTENT_LENGTH': '126'
HTTP_PRAGMA': 'no-cache'
HTTP_CACHE_CONTROL': 'no-cache'
HTTP_ACCEPT': 'application/json, text/plain, */*'

HTTP_ORIGIN': 'http://localhost:4213'

HTTP_AUTHORIZATION': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MzQ0OTM4MDksInN1YiI6eyIkJFVTUklETE9HSU4iOiIxNDQ5IiwiJCRVU1JOQU1FTE9HSU4iOiI5OSAtIHdlaXRlcmUgRmFjaGdydXBwZW4iLCIkJFVTUkNPREVMT0dJTiI6IkRXRSIsIiQkU0VTU0lPTkxPR0lEIjoxLCIkJFNJVEVJRCI6IjkwMDAwMDMyNzAiLCIkJFNJVEVHUlAiOiJMQUJPIiwiJCRURVNURkVBVFVSRUxJU1QiOiJURVNUUkVGTkVXXHUwMDFiTVVMVElERUJcdTAwMWJSRVRSSUVWRU5FV1x1MDAxYlJFU1JFRlx1MDAxYkxYUkJBQzJcdTAwMWJGQ1RURVNUXHUwMDFiQ09NVEVTVFx1MDAxYk5PTkVVTklRVUVCQ1x1MDAxYlBFUkZfTFhURVhUIiwiJCRMQU5HVUFHRSI6IiIsIkxBTkdVQUdFIjpudWxsfSwiZXhwIjoxNTM0NTgwMjA5LCJpbnN0YW5jZSI6ImRldiJ9.33Pid_GDPP6LUgoGzv_uU26QisBJNqoqsc7uTczdLvU'
HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
HTTP_CONTENT_TYPE': 'application/json'
HTTP_REFERER': 'http://localhost:4213/login'
HTTP_ACCEPT_ENCODING': 'gzip, deflate'
HTTP_ACCEPT_LANGUAGE': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6'
SCRIPT_NAME': '/lx/ui_api/dev/dwe/dwe/reporting'
wsgi.input': <uwsgi._Input object at 0x7fd792236300>
wsgi.version': (1, 0)
wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>
wsgi.run_once': False
wsgi.multithread': True
wsgi.multiprocess': False
wsgi.url_scheme': 'http'
uwsgi.version': b'2.0.15'
uwsgi.core': 3
uwsgi.node': b'lsdev01'
werkzeug.request': <Request 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1' [POST]>}
shallow': False
view_args': {'reportCode': 'DEMO_TECH_DEMO1'}
url_rule': <Rule '/report/generate/<reportCode>' (OPTIONS, POST) -> report_generate>
_parsed_content_type': ('application/json', {})
stream': <werkzeug.wsgi.LimitedStream object at 0x7fd78f7d7438>
_cached_data': b'{"mimetype":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","input":{"header":"Test 1","footer":"Test 2"}}'
_cached_json': {'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
input': {'header': 'Test 1'
footer': 'Test 2'}}
url': 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1'}

对比

<Request 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1' [POST]>
{'environ': {'QUERY_STRING': ''
REQUEST_METHOD': 'POST'
CONTENT_TYPE': 'application/json'
CONTENT_LENGTH': '126'
REQUEST_URI': '/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1'
PATH_INFO': '/report/generate/DEMO_TECH_DEMO1'
DOCUMENT_ROOT': '/usr/share/nginx/html'
SERVER_PROTOCOL': 'HTTP/1.1'
REQUEST_SCHEME': 'http'
REMOTE_ADDR': '192.168.69.18'
REMOTE_PORT': '55154'
SERVER_PORT': '80'
SERVER_NAME': 'lsdev01'
HTTP_HOST': 'lsdev01'
HTTP_ACCEPT_ENCODING': 'deflate, gzip'



HTTP_ACCEPT': 'application/json, text/plain, */*'
HTTP_REFERER': 'http://localhost:4213/login'
HTTP_ORIGIN': 'http://localhost:4213'
HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
HTTP_AUTHORIZATION': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MzQ0OTM4MDksInN1YiI6eyIkJFVTUklETE9HSU4iOiIxNDQ5IiwiJCRVU1JOQU1FTE9HSU4iOiI5OSAtIHdlaXRlcmUgRmFjaGdydXBwZW4iLCIkJFVTUkNPREVMT0dJTiI6IkRXRSIsIiQkU0VTU0lPTkxPR0lEIjoxLCIkJFNJVEVJRCI6IjkwMDAwMDMyNzAiLCIkJFNJVEVHUlAiOiJMQUJPIiwiJCRURVNURkVBVFVSRUxJU1QiOiJURVNUUkVGTkVXXHUwMDFiTVVMVElERUJcdTAwMWJSRVRSSUVWRU5FV1x1MDAxYlJFU1JFRlx1MDAxYkxYUkJBQzJcdTAwMWJGQ1RURVNUXHUwMDFiQ09NVEVTVFx1MDAxYk5PTkVVTklRVUVCQ1x1MDAxYlBFUkZfTFhURVhUIiwiJCRMQU5HVUFHRSI6IiIsIkxBTkdVQUdFIjpudWxsfSwiZXhwIjoxNTM0NTgwMjA5LCJpbnN0YW5jZSI6ImRldiJ9.33Pid_GDPP6LUgoGzv_uU26QisBJNqoqsc7uTczdLvU'

HTTP_CONTENT_TYPE': 'application/json'
HTTP_CONTENT_LENGTH': '126'


SCRIPT_NAME': '/lx/ui_api/dev/dwe/dwe/reporting'
wsgi.input': <uwsgi._Input object at 0x7fd792236300>
wsgi.version': (1, 0)
wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>
wsgi.run_once': False
wsgi.multithread': True
wsgi.multiprocess': False
wsgi.url_scheme': 'http'
uwsgi.version': b'2.0.15'
uwsgi.core': 0
uwsgi.node': b'lsdev01'
werkzeug.request': <Request 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1' [POST]>}
shallow': False
view_args': {'reportCode': 'DEMO_TECH_DEMO1'}
url_rule': <Rule '/report/generate/<reportCode>' (OPTIONS, POST) -> report_generate>
_parsed_content_type': ('application/json', {})
stream': <werkzeug.wsgi.LimitedStream object at 0x7fd78f7bcf98>
_cached_data': b'{"mimetype":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","input":{"header":"Test 1","footer":"Test 2"}}'
_cached_json': {'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
input': {'header': 'Test 1'
footer': 'Test 2'}}
url': 'http://lsdev01/lx/ui_api/dev/dwe/dwe/reporting/report/generate/DEMO_TECH_DEMO1'}

不过没什么特别的...

有人知道问题出在哪里吗?

编辑:我也试过 send_files,但我遇到了同样的问题。

该死的!我知道了!

我在服务器端编码为 base64:

return Response(base64.b64encode(output), mimetype=mimetype)

在 angular 中,我结合了一些在 so 上找到的函数:

  download(mimetype, data) {

    function s2ab(s) {
      var buf = new ArrayBuffer(s.length);
      var view = new Uint8Array(buf);
      for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
      return buf;
    }

    var blob = new Blob([s2ab(atob(data))], {type: mimetype});
    var objectUrl = URL.createObjectURL(blob);
    window.open(objectUrl);
  }

atob = 解码base64编码的字符串 s2ab = 字符串到 blob 的数组缓冲区

我认为这一切的根源是客户端和服务器之间的编码问题。但是我没有解释为什么!