From 308b593c6b9d6e1b5c43faffcd8240ab9137cf2e Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Fri, 7 Oct 2022 13:42:43 +0000 Subject: [PATCH 1/6] Fix (python): Fix miniclient.http_get() causing nginx to fail with `499` `499` happens on `nginx` when the client closes the connection before `nginx` replies. See: https://stackoverflow.com/questions/12973304/possible-reason-for-nginx-499-error-codes The solution is to close the connection after the response is received from the server. --- stats/miniclient.py | 76 +++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/stats/miniclient.py b/stats/miniclient.py index 7cafebc..5d55d5a 100644 --- a/stats/miniclient.py +++ b/stats/miniclient.py @@ -18,40 +18,46 @@ def http_get(host, port = 80, document = "/"): raise - http.writeline("GET %s HTTP/1.1" % str(document)) - http.writeline("Host: %s" % host) - http.writeline("User-Agent: GameSpyHTTP/1.0") - http.writeline("Connection: close") # do not keep-alive - http.writeline("") - http.shutdown() # be nice, tell the http server we're done sending the request - - # Determine Status - statusCode = 0 - status = string.split(http.readline()) - if status[0] != "HTTP/1.1": - print "MiniClient: Unknown status response (%s)" % str(status[0]) - try: - statusCode = string.atoi(status[1]) - except ValueError: - print "MiniClient: Non-numeric status code (%s)" % str(status[1]) - - #Extract Headers - headers = [] - while 1: - line = http.readline() - if not line: - break - headers.append(line) - - http.close() # all done - - #Check we got a valid HTTP response - if statusCode == 200: - return http.read() - else: - return "E\nH\terr\nD\tHTTP Error %s \"%s\"\n$\tERR\t$" % (str(statusCode), str(status[2])) - + http.writeline("GET %s HTTP/1.1" % str(document)) + http.writeline("Host: %s" % host) + http.writeline("User-Agent: GameSpyHTTP/1.0") + http.writeline("Connection: close") # do not keep-alive + http.writeline("") + + # Determine Status + statusCode = 0 + status = string.split(http.readline()) + if status[0] != "HTTP/1.1": + print "MiniClient: Unknown status response (%s)" % str(status[0]) + + try: + statusCode = string.atoi(status[1]) + except ValueError: + print "MiniClient: Non-numeric status code (%s)" % str(status[1]) + + #Extract Headers + headers = [] + while 1: + line = http.readline() + if not line: + break + headers.append(line) + + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done + + #Check we got a valid HTTP response + if statusCode == 200: + return http.read() + else: + return "E\nH\terr\nD\tHTTP Error %s \"%s\"\n$\tERR\t$" % (str(statusCode), str(status[2])) + + except Exception, e: + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done + raise + def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): @@ -76,7 +82,6 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): http.writeline("") http.writeline(str(snapshot)) http.writeline("") - http.shutdown() # be nice, tell the http server we're done sending the request # Check that SnapShot Arrives. # Determine Status @@ -98,6 +103,7 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): break headers.append(line) + http.shutdown() # be nice, tell the http server we're done sending the request http.close() # all done if statusCode == 200: @@ -106,6 +112,8 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): return "E\nH\terr\nD\tHTTP Error %s \"%s\"\n$\tERR\t$" % (str(statusCode), str(status[2])) except Exception, e: + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done raise class miniclient: From 176de4ddf9c614d41452442047cd8688b0e88a4c Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Fri, 7 Oct 2022 13:51:48 +0000 Subject: [PATCH 2/6] Fix (python): Fix http library to support chunked responses --- stats/miniclient.py | 54 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/stats/miniclient.py b/stats/miniclient.py index 5d55d5a..c97ff06 100644 --- a/stats/miniclient.py +++ b/stats/miniclient.py @@ -37,20 +37,34 @@ def http_get(host, port = 80, document = "/"): print "MiniClient: Non-numeric status code (%s)" % str(status[1]) #Extract Headers - headers = [] + headers = {} while 1: line = http.readline() if not line: break - headers.append(line) - - http.shutdown() # be nice, tell the http server we're done sending the request - http.close() # all done + split = line.split(":") + headers[split[0].strip()] = split[1].strip() #Check we got a valid HTTP response if statusCode == 200: - return http.read() + body = "" + if "Content-Length" in headers: + content_length = headers["Content-Length"] + body = http.read() + elif headers["Transfer-Encoding"] == "chunked": + while 1: + chunk_length = int(http.readline(), 16) + if chunk_length != 0: + body += http.read(chunk_length) + http.readline() # CRLF + if chunk_length == 0: + break + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done + return body else: + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done return "E\nH\terr\nD\tHTTP Error %s \"%s\"\n$\tERR\t$" % (str(statusCode), str(status[2])) except Exception, e: @@ -96,19 +110,33 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): print "MiniClient: Non-numeric status code (%s)" % str(status[1]) #Extract Headers - headers = [] + headers = {} while 1: line = http.readline() if not line: break - headers.append(line) - - http.shutdown() # be nice, tell the http server we're done sending the request - http.close() # all done - + split = line.split(":") + headers[split[0].strip()] = split[1].strip() + if statusCode == 200: - return http.read() + body = "" + if "Content-Length" in headers: + content_length = headers["Content-Length"] + body = http.read() + elif headers["Transfer-Encoding"] == "chunked": + while 1: + chunk_length = int(http.readline(), 16) + if chunk_length != 0: + body += http.read(chunk_length) + http.readline() # CRLF + if chunk_length == 0: + break + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done + return body else: + http.shutdown() # be nice, tell the http server we're done sending the request + http.close() # all done return "E\nH\terr\nD\tHTTP Error %s \"%s\"\n$\tERR\t$" % (str(statusCode), str(status[2])) except Exception, e: From deab8a57e81b48730b1fd4e270a6b778c65a84d0 Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Sat, 21 Jan 2023 09:43:16 +0000 Subject: [PATCH 3/6] Fix (python): Fix regression in `miniclient.http_get()` to read full response body if `Content-Length` or `Transfer-Encoding` HTTP Headers are absent This fixes a critical regression that causes the BF2 server to not process stats if the ASP webserver does not respond with `Content-Length` or `Transfer-Encoding` HTTP Headers. Fixes regression in #2. --- stats/miniclient.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/stats/miniclient.py b/stats/miniclient.py index c97ff06..f746e7b 100644 --- a/stats/miniclient.py +++ b/stats/miniclient.py @@ -51,7 +51,7 @@ def http_get(host, port = 80, document = "/"): if "Content-Length" in headers: content_length = headers["Content-Length"] body = http.read() - elif headers["Transfer-Encoding"] == "chunked": + elif "Transfer-Encoding" in headers and headers["Transfer-Encoding"] == "chunked": while 1: chunk_length = int(http.readline(), 16) if chunk_length != 0: @@ -59,6 +59,10 @@ def http_get(host, port = 80, document = "/"): http.readline() # CRLF if chunk_length == 0: break + else: + # No Content-Length nor Transfer-Encoding header. Read until EOF + body = http.read() + http.shutdown() # be nice, tell the http server we're done sending the request http.close() # all done return body @@ -123,7 +127,7 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): if "Content-Length" in headers: content_length = headers["Content-Length"] body = http.read() - elif headers["Transfer-Encoding"] == "chunked": + elif "Transfer-Encoding" in headers and headers["Transfer-Encoding"] == "chunked": while 1: chunk_length = int(http.readline(), 16) if chunk_length != 0: @@ -131,6 +135,10 @@ def http_postSnapshot(host, port = 80, document = "/", snapshot = ""): http.readline() # CRLF if chunk_length == 0: break + else: + # No Content-Length nor Transfer-Encoding header. Read until EOF + body = http.read() + http.shutdown() # be nice, tell the http server we're done sending the request http.close() # all done return body From e66e3594d35195aae5176f7d62953f5b66f94291 Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Sun, 22 Jan 2023 05:40:26 +0000 Subject: [PATCH 4/6] Feature (ci): Add github workflows --- .github/release-drafter.yml | 52 ++++++++++++++++++++++++++++++ .github/workflows/ci-master-pr.yml | 38 ++++++++++++++++++++++ README.md | 6 ++++ 3 files changed, 96 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/ci-master-pr.yml create mode 100644 README.md diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..f1d241d --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,52 @@ +name-template: 'v$RESOLVED_VERSION 🌈' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '🖊️ Refactors' + labels: + - 'refactor' + - title: '👗 Style' + labels: + - 'style' + - title: '📝 Documentation' + labels: + - 'docs' + - 'documentation' + - title: '🧰 Maintenance' + label: 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +version-resolver: + major: + labels: + # - 'major' + - 'breaking' + minor: + labels: + # - 'minor' + - 'feature' + - 'enhancement' + - 'refactor' + patch: + labels: + # - 'patch' + - 'fix' + - 'bugfix' + - 'bug' + - 'style' + - 'docs' + - 'documentation' + - 'chore' + default: patch +sort-by: title +template: | + ## Changes + + $CHANGES diff --git a/.github/workflows/ci-master-pr.yml b/.github/workflows/ci-master-pr.yml new file mode 100644 index 0000000..b80bc3a --- /dev/null +++ b/.github/workflows/ci-master-pr.yml @@ -0,0 +1,38 @@ +name: ci-master-pr + +on: + push: + branches: + - master + tags: + - '**' + pull_request: + branches: + - master + +jobs: + update-draft-release: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + publish: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-draft-release: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + publish: true + name: ${{ github.ref_name }} # E.g. 'master' or 'v1.2.3' + tag: ${{ github.ref_name }} # E.g. 'master' or 'v1.2.3' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..66da687 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# StatsPython + +[![github-actions](https://github.com/startersclan/StatsPython/workflows/ci-master-pr/badge.svg)](https://github.com/startersclan/StatsPython/actions) +[![github-release](https://img.shields.io/github/v/release/startersclan/StatsPython?style=flat-square)](https://github.com/startersclan/StatsPython/releases/) + +BF2Statistics [`3.x.x`](https://github.com/BF2Statistics/ASP) python files for the BF2 server. From b3de2f58f674f6072102ffd8c1d55fa1ea0bc576 Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Sat, 4 Mar 2023 10:49:22 +0000 Subject: [PATCH 5/6] Enhancement (ci): Add `change` label to `release-drafter.yml` --- .github/release-drafter.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index f1d241d..80c8b7b 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,14 +1,14 @@ -name-template: 'v$RESOLVED_VERSION 🌈' +name-template: 'v$RESOLVED_VERSION 🌈' tag-template: 'v$RESOLVED_VERSION' categories: - title: '🚀 Features' labels: - 'feature' - 'enhancement' + - 'change' - title: '🐛 Bug Fixes' labels: - 'fix' - - 'bugfix' - 'bug' - title: '🖊️ Refactors' labels: @@ -26,19 +26,16 @@ change-template: '- $TITLE @$AUTHOR (#$NUMBER)' version-resolver: major: labels: - # - 'major' - 'breaking' minor: labels: - # - 'minor' - 'feature' - 'enhancement' + - 'change' - 'refactor' patch: labels: - # - 'patch' - 'fix' - - 'bugfix' - 'bug' - 'style' - 'docs' From 9ba74a8173753e258e01f4a632d47f33bd146326 Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Wed, 22 Nov 2023 05:02:10 +0000 Subject: [PATCH 6/6] Docs: Add notice about moving files to ASP --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66da687..122b09c 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,8 @@ [![github-actions](https://github.com/startersclan/StatsPython/workflows/ci-master-pr/badge.svg)](https://github.com/startersclan/StatsPython/actions) [![github-release](https://img.shields.io/github/v/release/startersclan/StatsPython?style=flat-square)](https://github.com/startersclan/StatsPython/releases/) -BF2Statistics [`3.x.x`](https://github.com/BF2Statistics/ASP) python files for the BF2 server. +BF2Statistics [`3.x.x`](https://github.com/startersclan/ASP) python files for the BF2 server. + +## Moved + +Note that since `3.3.0`, the python files have been moved to https://github.com/startersclan/ASP.