Skip to content

uaiohttpclient does not support POST/PUT data #700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
GM-Script-Writer-62850 opened this issue Jul 8, 2023 · 3 comments
Closed

uaiohttpclient does not support POST/PUT data #700

GM-Script-Writer-62850 opened this issue Jul 8, 2023 · 3 comments

Comments

@GM-Script-Writer-62850
Copy link

GM-Script-Writer-62850 commented Jul 8, 2023

Usage: await uaiohttpclient.request(method,url)

method = "POST"
url = "http://10.0.0.69:8080/input.php"
header = "Content-Type: application/json; charset=utf-8"
data = '{"foo":"bar"}'

Function has no way to take data or set the header content type for that matter

@GM-Script-Writer-62850
Copy link
Author

i think i will use this lib instead: https://github.com/Carglglz/asyncmd/blob/main/async_modules/async_urequests/async_urequests.py

Here is my untested attempt at a fix:

import uasyncio as asyncio

class ClientResponse:
    def __init__(self, reader):
        self.content = reader

    def read(self, sz=-1):
        return (yield from self.content.read(sz))

    def __repr__(self):
        return "<ClientResponse %d %s>" % (self.status, self.headers)


class ChunkedClientResponse(ClientResponse):
    def __init__(self, reader):
        self.content = reader
        self.chunk_size = 0

    def read(self, sz=4 * 1024 * 1024):
        if self.chunk_size == 0:
            l = yield from self.content.readline()
            # print("chunk line:", l)
            l = l.split(b";", 1)[0]
            self.chunk_size = int(l, 16)
            # print("chunk size:", self.chunk_size)
            if self.chunk_size == 0:
                # End of message
                sep = yield from self.content.read(2)
                assert sep == b"\r\n"
                return b""
        data = yield from self.content.read(min(sz, self.chunk_size))
        self.chunk_size -= len(data)
        if self.chunk_size == 0:
            sep = yield from self.content.read(2)
            assert sep == b"\r\n"
        return data

    def __repr__(self):
        return "<ChunkedClientResponse %d %s>" % (self.status, self.headers)


def request_raw(method, headers, url, content):
    try:
        proto, dummy, host, path = url.split("/", 3)
    except ValueError:
        proto, dummy, host = url.split("/", 2)
        path = ""
    if proto != "http:":# but WHY there is a ssl library and what about ftp?
        raise ValueError("Unsupported protocol: %s" % proto)
    host_port=host.split(':', 1)
    if len(host_port) == 1:
        host_port.append(80)
    reader, writer = yield from asyncio.open_connection(host_port[0], host_port[1])# (host, port)
    if content:
        headers.append("Content-Length: %d" % len(content))
    else:
        content=""
    if len(headers): # is there a faster way?
        headers.append("")# Avoid string operations
        headers="\r\n".join(headers)
    else:
        headers=""
    # Use protocol 1.0, because 1.1 always allows to use chunked transfer-encoding
    # But explicitly set Connection: close, even though this should be default for 1.0,
    # because some servers misbehave w/o it.
    query = "%s /%s HTTP/1.0\r\nHost: %s\r\n%sConnection: close\r\nUser-Agent: compat\r\n\r\n%s" % (
        method,
        path,
        host,
        headers,
        content
    )
    yield from writer.awrite(query.encode("latin-1"))
    #    yield from writer.aclose()
    return reader

# usage:
#   request("GET", "http://example.com/example.html?param1=value1&param2=value2"):
#   request("GET", "http://example.com/example.html?param1=value1&param2=value2"):
#   request("POST", "http://example.com/example.php", ["Content-Type: application/json; charset=utf-8"], '{"param1":"value2","param2":"value2"}')
#   request("POST", "http://example.com/example.php", ["Content-Type: application/x-www-form-urlencoded; charset=utf-8"], 'param1=value1&param2=value2')
def request(method, url, headers=[], content=None):
    redir_cnt = 0
    redir_url = None
    while redir_cnt < 2:
        reader = yield from request_raw(method, headers, url, content)
        headers = [] # reply headers, above is outbound
        sline = yield from reader.readline()
        sline = sline.split(None, 2)
        status = int(sline[1])
        chunked = False
        while True:
            line = yield from reader.readline()
            if not line or line == b"\r\n":
                break
            headers.append(line)
            if line.startswith(b"Transfer-Encoding:"):
                if b"chunked" in line:
                    chunked = True
            elif line.startswith(b"Location:"):
                url = line.rstrip().split(None, 1)[1].decode("latin-1")

        if 301 <= status <= 303:
            redir_cnt += 1
            yield from reader.aclose()
            continue
        break

    if chunked:
        resp = ChunkedClientResponse(reader)
    else:
        resp = ClientResponse(reader)
    resp.status = status
    resp.headers = headers
    return resp

Changes:

  • allow use of port other than 80 for http
  • support sending headers
  • support sending post data

Hopefully i got query right in request_raw

@rroemhild
Copy link

I did the same, then found your issue about it 🙈

Here is my attempt, only json, no header support and allow to use a port other than 80.

import uasyncio as asyncio


class ClientResponse:
    def __init__(self, reader):
        self.content = reader

    def read(self, sz=-1):
        return (yield from self.content.read(sz))

    def __repr__(self):
        return "<ClientResponse %d %s>" % (self.status, self.headers)


class ChunkedClientResponse(ClientResponse):
    def __init__(self, reader):
        self.content = reader
        self.chunk_size = 0

    def read(self, sz=4 * 1024 * 1024):
        if self.chunk_size == 0:
            l = yield from self.content.readline()
            # print("chunk line:", l)
            l = l.split(b";", 1)[0]
            self.chunk_size = int(l, 16)
            # print("chunk size:", self.chunk_size)
            if self.chunk_size == 0:
                # End of message
                sep = yield from self.content.read(2)
                assert sep == b"\r\n"
                return b""
        data = yield from self.content.read(min(sz, self.chunk_size))
        self.chunk_size -= len(data)
        if self.chunk_size == 0:
            sep = yield from self.content.read(2)
            assert sep == b"\r\n"
        return data

    def __repr__(self):
        return "<ChunkedClientResponse %d %s>" % (self.status, self.headers)


def request_raw(method, url, json=None):
    data = None

    try:
        proto, dummy, host, path = url.split("/", 3)
    except ValueError:
        proto, dummy, host = url.split("/", 2)
        path = ""
    if proto == "http:":
        port = 80
    else:
        raise ValueError("Unsupported protocol: " + proto)

    if ":" in host:
        host, port = host.split(":", 1)
        port = int(port)

    reader, writer = yield from asyncio.open_connection(host, port)
    # Use protocol 1.0, because 1.1 always allows to use chunked transfer-encoding
    # But explicitly set Connection: close, even though this should be default for 1.0,
    # because some servers misbehave w/o it.
    yield from writer.awrite(b"{} /{} HTTP/1.0\r\nHost: {}\r\nUser-Agent: compat\r\n".format(method, path, host))

    if json is not None:
        import ujson

        data = ujson.dumps(json)
        yield from writer.awrite(b"Content-Type: application/json\r\n")
        yield from writer.awrite(b"Content-Length: {}\r\n".format(len(data)))

    yield from writer.awrite(b"Connection: close\r\n\r\n")

    if data:
        yield from writer.awrite(data)

    # yield from writer.aclose()
    return reader


def request(method, url, json=None):
    redir_cnt = 0
    redir_url = None
    while redir_cnt < 2:
        reader = yield from request_raw(method, url, json)
        headers = []
        sline = yield from reader.readline()
        sline = sline.split(None, 2)
        status = int(sline[1])
        chunked = False
        while True:
            line = yield from reader.readline()
            if not line or line == b"\r\n":
                break
            headers.append(line)
            if line.startswith(b"Transfer-Encoding:"):
                if b"chunked" in line:
                    chunked = True
            elif line.startswith(b"Location:"):
                url = line.rstrip().split(None, 1)[1].decode("latin-1")

        if 301 <= status <= 303:
            redir_cnt += 1
            yield from reader.aclose()
            continue
        break

    if chunked:
        resp = ChunkedClientResponse(reader)
    else:
        resp = ClientResponse(reader)
    resp.status = status
    resp.headers = headers
    return resp

Works for me since a few days without problems

@jonnor
Copy link

jonnor commented Aug 25, 2024

uiaiohttpclient has now been replaced by aiohttp. And it now implements POST/PUT, thanks to #778

So I believe this issue can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants