HTTP Server: Failed to decode gzip: Failed to decompress data: gzip: invalid header

I’m stuck on the last stage with the Failed to decode gzip: Failed to decompress data: gzip: invalid header error.

Below are the error logs:

remote: [tester::#CR8] Running tests for Stage #CR8 (HTTP Compression - Gzip compression)
remote: [tester::#CR8] Running program
remote: [tester::#CR8] $ ./your_server.sh
remote: [tester::#CR8] Connected to localhost port 4221
remote: [tester::#CR8] $ curl -v http://localhost:4221/echo/raspberry -H "Accept-Encoding: gzip"
remote: [tester::#CR8] > GET /echo/raspberry HTTP/1.1
remote: [tester::#CR8] > Host: localhost:4221
remote: [tester::#CR8] > Accept-Encoding: gzip
remote: [tester::#CR8] >
remote: [tester::#CR8] Sent bytes: "GET /echo/raspberry HTTP/1.1\r\nHost: localhost:4221\r\nAccept-Encoding: gzip\r\n\r\n"
remote: [your_program] HTTP/1.1 200 OK
remote: [your_program] Content-Type: text/plain
remote: [your_program] Content-Length: 29
remote: [your_program] Content-Encoding: gzip
remote: [your_program]
remote: [your_program] b'\x1f\x8b\x08\x00W\x9ddf\x02\xff+J,.HJ-*\xaa\x04\x00a\xd5\x10~\t\x00\x00\x00'
remote: [tester::#CR8] Received bytes: "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 29\r\nContent-Encoding: gzip\r\n\r\nb'\\x1f\\x8b\\x08\\x00W\\x9ddf\\x02"
remote: [tester::#CR8] < HTTP/1.1 200 OK
remote: [tester::#CR8] < Content-Type: text/plain
remote: [tester::#CR8] < Content-Length: 29
remote: [tester::#CR8] < Content-Encoding: gzip
remote: [tester::#CR8] <
remote: [tester::#CR8] < b'\x1f\x8b\x08\x00W\x9ddf\x02
remote: [tester::#CR8] <
remote: [tester::#CR8] Received response with 200 status code
remote: [tester::#CR8] ✓ Content-Encoding header is present
remote: [tester::#CR8] ✓ Content-Length header is present
remote: [tester::#CR8] Failed to decode gzip: Failed to decompress data: gzip: invalid header
remote: [tester::#CR8] Test failed
remote: [tester::#CR8] Terminating program
remote: [tester::#CR8] Program terminated successfully

And here’s my code:

import argparse
import gzip
import os
import socket
import threading


def request_handler(conn: socket.socket, args: argparse.Namespace) -> None:
    with conn:
        data = conn.recv(1024).decode()
        response = process_request(data, args)
        conn.sendall(response.encode())


def process_request(data: str, args: argparse.Namespace) -> str:
    request_content = data.split("\r\n")
    request_method, request_target, _ = request_content[0].split()

    # Process headers
    request_headers = tuple([tuple(param.split(": ")) for param in request_content[2:-2]])
    request_headers = dict((k.lower(), v) for k, v in request_headers)

    request_body = request_content[-1]

    # Handle routes
    response_params = None

    if request_target == "/":
        response_params = {"status_code": "200", "reason": "OK", "header": "", "body": ""}

    if request_target.lower().startswith("/echo/"):
        echo_str = request_target[6:]  # Remove '/echo/'

        # Compression scheme: gzip
        compression_scheme = ""
        if request_headers.get("accept-encoding"):
            if "gzip" in str(request_headers.get("accept-encoding")):
                compression_scheme = "Content-Encoding: gzip\r\n"
                echo_str = gzip.compress(echo_str.encode())

        response_params = {
            "status_code": "200",
            "reason": "OK",
            "header": f"Content-Type: text/plain\r\nContent-Length: {str(len(echo_str))}\r\n{compression_scheme}",
            "body": echo_str,
        }

    if request_target.lower().startswith("/files/"):
        filepath = os.path.join(args.directory, request_target.replace("/files/", ""))

        # GET Request
        if request_method == "GET":
            if os.path.isfile(filepath):
                with open(filepath, "r") as f:
                    f_content = f.read()

                response_params = {
                    "status_code": "200",
                    "reason": "OK",
                    "header": f"Content-Type: application/octet-stream\r\nContent-Length: {len(f_content)}\r\n",
                    "body": f_content,
                }

        # POST request
        if request_method == "POST":
            with open(filepath, "w") as f:
                f.write(request_body)

            response_params = {
                "status_code": "201",
                "reason": "Created",
                "header": f"Content-Type: application/octet-stream\r\nContent-Length: {len(request_body)}\r\n",
                "body": request_body,
            }

    if request_target.lower() == "/user-agent":
        response_params = {
            "status_code": "200",
            "reason": "OK",
            "header": f"Content-Type: text/plain\r\nContent-Length: {len(str(request_headers.get('user-agent')))}\r\n",
            "body": request_headers.get("user-agent"),
        }

    if not response_params:
        response_params = {"status_code": "404", "reason": "Not Found", "header": "", "body": ""}

    response = f"HTTP/1.1 {response_params['status_code']} {response_params['reason']}\r\n{response_params['header']}\r\n{response_params['body']}"
    print(response)

    return response


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--directory", type=str, required=False)
    args = parser.parse_args()

    server_socket = socket.create_server(("localhost", 4221), reuse_port=True)

    while True:
        conn, addr = server_socket.accept()
        threading.Thread(target=request_handler, args=(conn, args)).start()


if __name__ == "__main__":
    main()

I’ve checked others solutions and it seems to me that I am not doing anything different. If anyone could give me some pointers, it would be greatly appreciated.

I’m not sure but it does look like you are sending the string representation of the gzip result instead of the actual bytes.

Looking at it a little more I’m pretty sure this is your error:

response = f"HTTP/1.1 {response_params['status_code']} {response_params['reason']}\r\n{response_params['header']}\r\n{response_params['body']}"

The {response_params['body']} creates a string representation of the gzip bytes which is not what you want to send.

2 Likes

Thank you! That worked!

Didn’t even cross my mind I was sending the string representation of the bytes!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.