Hello community,

here is the log from the commit of package python-bjoern for openSUSE:Factory 
checked in at 2020-06-24 15:49:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-bjoern (Old)
 and      /work/SRC/openSUSE:Factory/.python-bjoern.new.2956 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-bjoern"

Wed Jun 24 15:49:18 2020 rev:9 rq:816735 version:3.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-bjoern/python-bjoern.changes      
2019-07-08 16:37:24.304651680 +0200
+++ /work/SRC/openSUSE:Factory/.python-bjoern.new.2956/python-bjoern.changes    
2020-06-24 15:49:23.708533019 +0200
@@ -1,0 +2,8 @@
+Wed Jun 24 01:48:04 UTC 2020 - Steve Kowalik <steven.kowa...@suse.com>
+
+- Update to 3.1.0:
+  * #169 Fix blocking accept() (Ionut Negru)
+  * #164 Add support for statsd metrics/events (Mohammad Gufran)
+  * #162 Fix Expect: 100-Continue support (Tom Brennan) 
+
+-------------------------------------------------------------------

Old:
----
  bjoern-3.0.1.tar.gz

New:
----
  bjoern-3.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-bjoern.spec ++++++
--- /var/tmp/diff_new_pack.gyNPFA/_old  2020-06-24 15:49:24.512536390 +0200
+++ /var/tmp/diff_new_pack.gyNPFA/_new  2020-06-24 15:49:24.516536407 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-bjoern
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,12 +18,11 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-bjoern
-Version:        3.0.1
+Version:        3.1.0
 Release:        0
 Summary:        A screamingly fast Python 2 + 3 WSGI server written in C
 License:        BSD-2-Clause
-Group:          Development/Languages/Python
-Url:            https://github.com/jonashaag/bjoern
+URL:            https://github.com/jonashaag/bjoern
 Source:         
https://files.pythonhosted.org/packages/source/b/bjoern/bjoern-%{version}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module setuptools}

++++++ bjoern-3.0.1.tar.gz -> bjoern-3.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/.gitmodules new/bjoern-3.1.0/.gitmodules
--- old/bjoern-3.0.1/.gitmodules        2014-11-11 21:31:07.000000000 +0100
+++ new/bjoern-3.1.0/.gitmodules        2019-10-19 14:58:30.000000000 +0200
@@ -1,3 +1,6 @@
 [submodule "http-parser"]
   path = http-parser
   url = git://github.com/joyent/http-parser
+[submodule "statsd-c-client"]
+  path = statsd-c-client
+  url = https://github.com/romanbsd/statsd-c-client
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/CHANGELOG new/bjoern-3.1.0/CHANGELOG
--- old/bjoern-3.0.1/CHANGELOG  2019-06-07 12:31:44.000000000 +0200
+++ new/bjoern-3.1.0/CHANGELOG  2019-11-03 10:44:04.000000000 +0100
@@ -1,3 +1,8 @@
+3.1.0 (Nov 3, 2019)
+    - #169 Fix blocking accept() (Ionut Negru)
+    - #164 Add support for statsd metrics/events (Mohammad Gufran)
+    - #162 Fix Expect: 100-Continue support (Tom Brennan)
+
 3.0.1 (June 6, 2019)
     - Fix #158, #160: Correct string type for WSGI environ
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/Makefile new/bjoern-3.1.0/Makefile
--- old/bjoern-3.0.1/Makefile   2019-06-07 12:29:11.000000000 +0200
+++ new/bjoern-3.1.0/Makefile   2019-11-03 10:43:15.000000000 +0100
@@ -9,18 +9,18 @@
 HTTP_PARSER_OBJ = $(HTTP_PARSER_DIR)/http_parser.o
 HTTP_PARSER_SRC = $(HTTP_PARSER_DIR)/http_parser.c
 
-objects                = $(HTTP_PARSER_OBJ) \
+STATSD_CLIENT_DIR = statsd-c-client
+STATSD_CLIENT_OBJ = $(STATSD_CLIENT_DIR)/statsd-client.o
+STATSD_CLIENT_SRC = $(STATSD_CLIENT_DIR)/statsd-client.c
+
+objects                = $(HTTP_PARSER_OBJ) $(STATSD_CLIENT_OBJ) \
                  $(patsubst $(SOURCE_DIR)/%.c, $(BUILD_DIR)/%.o, \
                             $(wildcard $(SOURCE_DIR)/*.c))
 
-CPPFLAGS       += $(PYTHON_INCLUDE) -I . -I $(SOURCE_DIR) -I $(HTTP_PARSER_DIR)
+CPPFLAGS       += $(PYTHON_INCLUDE) -I . -I $(SOURCE_DIR) -I 
$(HTTP_PARSER_DIR) -I $(STATSD_CLIENT_DIR)
 CFLAGS         += $(FEATURES) -std=c99 -fno-strict-aliasing -fcommon -fPIC 
-Wall
 LDFLAGS                += $(PYTHON_LDFLAGS) -l ev -shared -fcommon
 
-ifneq ($(WANT_SENDFILE), no)
-FEATURES       += -D WANT_SENDFILE
-endif
-
 ifneq ($(WANT_SIGINT_HANDLING), no)
 FEATURES       += -D WANT_SIGINT_HANDLING
 endif
@@ -33,6 +33,17 @@
 FEATURES       += -D SIGNAL_CHECK_INTERVAL=0.1
 endif
 
+ifeq ($(WANT_STATSD), yes)
+FEATURES       += -D WANT_STATSD
+else
+filter_out     = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
+objects                := $(call filter_out,statsd,$(objects))
+endif
+
+ifeq ($(WANT_STATSD_TAGS), yes)
+FEATURES       += -D WANT_STATSD_TAGS
+endif
+
 all: prepare-build $(objects) _bjoernmodule
 
 print-env:
@@ -48,13 +59,14 @@
        CFLAGS='-Os' make
 
 _bjoernmodule:
+       @echo ' -> ' $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(objects) -o 
$(BUILD_DIR)/_bjoern.so
        @$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(objects) -o 
$(BUILD_DIR)/_bjoern.so
        @PYTHONPATH=$$PYTHONPATH:$(BUILD_DIR) ${PYTHON} -c "import bjoern"
 
 again: clean all
 
 debug:
-       CFLAGS='-D DEBUG -g' make again
+       CFLAGS='${CFLAGS} -D DEBUG -g' make again
 
 $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c
        @echo ' -> ' $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
@@ -106,3 +118,6 @@
 
 $(HTTP_PARSER_OBJ):
        $(MAKE) -C $(HTTP_PARSER_DIR) http_parser.o CFLAGS_DEBUG_EXTRA=-fPIC 
CFLAGS_FAST_EXTRA=-fPIC
+
+$(STATSD_CLIENT_OBJ):
+       $(MAKE) -C $(STATSD_CLIENT_DIR) statsd-client.o 
CFLAGS_DEBUG_EXTRA=-fPIC CFLAGS_FAST_EXTRA=-fPIC
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/PKG-INFO new/bjoern-3.1.0/PKG-INFO
--- old/bjoern-3.0.1/PKG-INFO   2019-06-07 12:32:21.000000000 +0200
+++ new/bjoern-3.1.0/PKG-INFO   2019-11-03 10:45:13.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bjoern
-Version: 3.0.1
+Version: 3.1.0
 Summary: A screamingly fast Python 2 + 3 WSGI server written in C.
 Home-page: https://github.com/jonashaag/bjoern
 Author: Jonas Haag
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/README.rst new/bjoern-3.1.0/README.rst
--- old/bjoern-3.0.1/README.rst 2017-08-31 18:52:13.000000000 +0200
+++ new/bjoern-3.1.0/README.rst 2019-10-19 14:58:30.000000000 +0200
@@ -42,17 +42,27 @@
    # Bind to abstract Unix socket: (Linux only)
    bjoern.run(wsgi_application, 'unix:@socket_name')
 
+   # Enable statsd metrics. See instrumentation.md for details.
+   bjoern.run(wsgi_application, host, port, statsd=...)
+
 Alternatively, the mainloop can be run separately::
 
    bjoern.listen(wsgi_application, host, port)
    bjoern.run()
-   
+
+   # With metrics. See instrumentation.md for details.
+   bjoern.listen(wsgi_application, host, port)
+   bjoern.run(statsd=...)
+
 You can also simply pass a Python socket(-like) object. Note that you are 
responsible
 for initializing and cleaning up the socket in that case. ::
 
    bjoern.server_run(socket_object, wsgi_application)
    bjoern.server_run(filedescriptor_as_integer, wsgi_application)
 
+   # This needs manual compilation with `WANT_STATSD=yes`
+   bjoern.server_run(socket_object, wsgi_application, enable_statsd=True)
+
 .. _WSGI:         http://www.python.org/dev/peps/pep-0333/
 .. _libev:        http://software.schmorp.de/pkg/libev.html
 .. _http-parser:  https://github.com/joyent/http-parser
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/_bjoernmodule.c 
new/bjoern-3.1.0/bjoern/_bjoernmodule.c
--- old/bjoern-3.0.1/bjoern/_bjoernmodule.c     2019-06-07 12:32:00.000000000 
+0200
+++ new/bjoern-3.1.0/bjoern/_bjoernmodule.c     2019-11-03 10:44:28.000000000 
+0100
@@ -3,6 +3,10 @@
 #include "wsgi.h"
 #include "filewrapper.h"
 
+#ifdef WANT_STATSD
+#include "statsd-client.h"
+#endif
+
 static PyObject*
 run(PyObject* self, PyObject* args)
 {
@@ -10,9 +14,31 @@
 
   PyObject* socket;
 
-  if(!PyArg_ParseTuple(args, "OO:server_run", &socket, &info.wsgi_app)) {
+#ifdef WANT_STATSD
+  info.statsd = NULL;
+  int statsd_enabled;
+  char* statsd_host;
+  int statsd_port;
+  char* statsd_ns;
+  char* statsd_tags = NULL;
+
+  if(!PyArg_ParseTuple(args, "OOiziz|z:server_run", &socket, &info.wsgi_app,
+                       &statsd_enabled, &statsd_host, &statsd_port, 
&statsd_ns, &statsd_tags)) {
+    return NULL;
+  }
+#else
+  char* ignored_str = NULL;
+  int ignored_int = 0;
+
+  if(!PyArg_ParseTuple(args, "OO|izizz:server_run", &socket, &info.wsgi_app, 
&ignored_int,
+                       &ignored_str, &ignored_int, &ignored_str, 
&ignored_str)) {
     return NULL;
   }
+  if (ignored_str != NULL || ignored_int != 0) {
+    PyErr_Format(PyExc_TypeError, "Unexpected statsd_* arguments (forgot to 
compile with statsd support?)");
+    return NULL;
+  }
+#endif
 
   info.sockfd = PyObject_AsFileDescriptor(socket);
   if (info.sockfd < 0) {
@@ -32,9 +58,38 @@
     }
   }
 
+#ifdef WANT_STATSD
+  if (statsd_enabled) {
+      if (statsd_host == NULL || *statsd_host == '\0') {
+        statsd_host = "127.0.0.1";
+      }
+
+      if (statsd_ns == NULL || *statsd_ns == '\0') {
+        info.statsd = statsd_init(statsd_host, statsd_port);
+      } else {
+        info.statsd = statsd_init_with_namespace(statsd_host, statsd_port, 
statsd_ns);
+      }
+#ifdef WANT_STATSD_TAGS
+      info.statsd_tags = statsd_tags;
+      DBG("Statsd: host=%s, port=%d, ns=%s, tags=%s", statsd_host, 
statsd_port, statsd_ns, statsd_tags);
+#else
+      DBG("Statsd: host=%s, port=%d, ns=%s", statsd_host, statsd_port, 
statsd_ns);
+#endif
+  } else {
+      DBG("Statsd disabled");
+  }
+
+
+#endif
+
   _initialize_request_module(&info);
+
   server_run(&info);
 
+#ifdef WANT_STATSD
+  statsd_finalize(info.statsd);
+#endif
+
   Py_RETURN_NONE;
 }
 
@@ -73,17 +128,45 @@
   Py_INCREF(&FileWrapper_Type);
   Py_INCREF(&StartResponse_Type);
 
+  PyObject* features = PyDict_New();
+
+#ifdef WANT_SIGNAL_HANDLING
+  PyDict_SetItemString(features, "has_signal_handling", Py_True);
+#else
+  PyDict_SetItemString(features, "has_signal_handling", Py_False);
+#endif
+
+#ifdef WANT_SIGINT_HANDLING
+  PyDict_SetItemString(features, "has_sigint_handling", Py_True);
+#else
+  PyDict_SetItemString(features, "has_sigint_handling", Py_False);
+#endif
+
+#ifdef WANT_STATSD
+  PyDict_SetItemString(features, "has_statsd", Py_True);
+#else
+  PyDict_SetItemString(features, "has_statsd", Py_False);
+#endif
+
+#ifdef WANT_STATSD_TAGS
+  PyDict_SetItemString(features, "has_statsd_tags", Py_True);
+#else
+  PyDict_SetItemString(features, "has_statsd_tags", Py_False);
+#endif
+
 #if PY_MAJOR_VERSION >= 3
   PyObject* bjoern_module = PyModule_Create(&module);
   if (bjoern_module == NULL) {
     return NULL;
   }
-
-  PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 0, 
1));
-  return bjoern_module;
 #else
   PyObject* bjoern_module = Py_InitModule("_bjoern", Bjoern_FunctionTable);
-  PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 0, 
1));
 #endif
 
+  PyModule_AddObject(bjoern_module, "features", features);
+  PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 1, 
0));
+
+#if PY_MAJOR_VERSION >= 3
+  return bjoern_module;
+#endif
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/common.h 
new/bjoern-3.1.0/bjoern/common.h
--- old/bjoern-3.0.1/bjoern/common.h    2018-08-04 15:14:38.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/common.h    2019-10-19 14:58:30.000000000 +0200
@@ -14,7 +14,12 @@
 
 typedef struct { char* data; size_t len; } string;
 
-enum http_status { HTTP_BAD_REQUEST = 1, HTTP_LENGTH_REQUIRED, 
HTTP_SERVER_ERROR };
+enum http_status {
+  HTTP_BAD_REQUEST = 1,
+  HTTP_LENGTH_REQUIRED,
+  HTTP_EXPECTATION_FAILED,
+  HTTP_SERVER_ERROR
+};
 
 size_t unquote_url_inplace(char* url, size_t len);
 void _init_common(void);
@@ -52,4 +57,27 @@
   #define assert(...) do{}while(0)
 #endif
 
+#ifdef WANT_STATSD
+  #include "statsd-client.h"
+
+  #ifdef WANT_STATSD_TAGS
+    #include "statsd_tags.h"
+    #define STATSD_INCREMENT(name) \
+      do { \
+        DBG("statisd.inc: %s", name); \
+        statsd_inc_with_tags(((ThreadInfo*) 
ev_userdata(mainloop))->server_info->statsd, \
+                             name, \
+                             ((ThreadInfo*) 
ev_userdata(mainloop))->server_info->statsd_tags); \
+      } while (0)
+  #else
+    #define STATSD_INCREMENT(name) \
+      do { \
+        DBG("statisd.inc: %s", name); \
+        statsd_inc(((ThreadInfo*) ev_userdata(mainloop))->server_info->statsd, 
name, 1.0); \
+      } while (0)
+  #endif
+#else
+  #define STATSD_INCREMENT(name) DBG("statsd.inc: %s", name)
+#endif
+
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/py2py3.h 
new/bjoern-3.1.0/bjoern/py2py3.h
--- old/bjoern-3.0.1/bjoern/py2py3.h    2019-05-27 16:04:07.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/py2py3.h    2019-09-12 09:31:24.000000000 +0200
@@ -18,6 +18,7 @@
 #define _PEP3333_String_FromFormat(...) PyUnicode_FromFormat(__VA_ARGS__)
 #define _PEP3333_String_GET_SIZE(u) PyUnicode_GET_LENGTH(u)
 #define _PEP3333_String_Concat(u1, u2) PyUnicode_Concat(u1, u2)
+#define _PEP3333_String_CompareWithASCIIString(o, c_str) 
PyUnicode_CompareWithASCIIString(o, c_str)
 
 #else
 
@@ -53,6 +54,11 @@
 
   return ret;
 }
+
+static int _PEP3333_String_CompareWithASCIIString(PyObject *o, const char 
*c_str)
+{
+  return memcmp(_PEP3333_Bytes_AS_DATA(o), c_str, _PEP3333_Bytes_GET_SIZE(o));
+}
 #endif
 
 #endif /* _PY2PY3_H */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/request.c 
new/bjoern-3.1.0/bjoern/request.c
--- old/bjoern-3.0.1/bjoern/request.c   2019-06-07 12:29:15.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/request.c   2019-09-12 09:31:24.000000000 +0200
@@ -180,7 +180,49 @@
   if(!PARSER->invalid_header) {
     /* Set header, or append data to header if this is not the first call */
     _set_or_append_header(REQUEST->headers, PARSER->field, value, len);
+
+    /*
+    ** HTTP/1.1 describes the header `Expect: 100-continue`, which provides
+    ** a mechanism for a client to request permission from a server to send
+    ** the rest of the request (i.e., the body). This restriction is entirely
+    ** client-side in that, regardless of this header existing, the client
+    ** can opt to send the body immediately (i.e., contradicting the header).
+    ** In that case, the header MAY be ignored (according to the RFC). It's
+    ** important to note that this mechanism is not intended to mitigate DoS
+    ** attacks, etc. It's an optional client-side courtesy only.
+    **
+    ** Some popular clients (e.g., cURL) do in fact wait for an indefinite
+    ** time period before sending the rest of the request, and so it is
+    ** appropriate to respond with `HTTP/1.1 100 Continue` in that case.
+    **
+    ** In the future, it may be a reasonable improvement to provide a callback
+    ** API to provide a mechanism for calling code (i.e., Python) to determine
+    ** whether continuation is appropriate based on the existing headers, but
+    ** currently the behavior implemented here is to respond with 100-continue
+    ** automatically as soon as the Expect header is detected, or
+    ** `417 Expectation Failed` if the header contains anything other than
+    ** "100-continue".
+    **
+    ** see https://tools.ietf.org/html/rfc2616#page-48 for specifics.
+    */
+    if (REQUEST->state.expect_continue || REQUEST->state.error_code)
+        return 0;
+
+    if (parser->http_major > 0 && parser->http_minor > 0) {
+      bool field_is_http_expect = !_PEP3333_String_CompareWithASCIIString(
+        PARSER->field, "HTTP_EXPECT"
+      );
+
+      if (field_is_http_expect) {
+        if (!strncmp(value, "100-continue", len)) {
+          REQUEST->state.expect_continue = true;
+        } else {
+          REQUEST->state.error_code = HTTP_EXPECTATION_FAILED;
+        }
+      }
+    }
   }
+
   return 0;
 }
 
@@ -189,6 +231,14 @@
 {
   PyObject *body;
 
+  /*
+  ** If you're reading the body, you're not waiting for it, so
+  ** no need to respond 100-continue.
+  ** See RFC 2616 https://tools.ietf.org/html/rfc2616#page-49 for details.
+  ** See also comment above in `on_header_value`.
+  */
+  REQUEST->state.expect_continue = false;
+
   body = PyDict_GetItem(REQUEST->headers, _wsgi_input);
   if (body == NULL) {
     if(!parser->content_length) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/request.h 
new/bjoern-3.1.0/bjoern/request.h
--- old/bjoern-3.0.1/bjoern/request.h   2018-06-01 11:06:59.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/request.h   2019-09-12 09:31:24.000000000 +0200
@@ -16,6 +16,7 @@
   unsigned keep_alive : 1;
   unsigned response_length_unknown : 1;
   unsigned chunked_response : 1;
+  unsigned expect_continue : 1;
 } request_state;
 
 typedef struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/server.c 
new/bjoern-3.1.0/bjoern/server.c
--- old/bjoern-3.0.1/bjoern/server.c    2019-05-07 15:13:43.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/server.c    2019-11-03 10:42:57.000000000 +0100
@@ -17,25 +17,32 @@
 #include "common.h"
 #include "wsgi.h"
 #include "server.h"
-
 #include "py2py3.h"
 
+#ifdef WANT_STATSD
+#include "statsd-client.h"
+#endif
+
 #define READ_BUFFER_SIZE 64*1024
 #define Py_XCLEAR(obj) do { if(obj) { Py_DECREF(obj); obj = NULL; } } while(0)
 #define GIL_LOCK(n) PyGILState_STATE _gilstate_##n = PyGILState_Ensure()
 #define GIL_UNLOCK(n) PyGILState_Release(_gilstate_##n)
 
-static const char* http_error_messages[4] = {
+static const char* http_error_messages[5] = {
   NULL, /* Error codes start at 1 because 0 means "no error" */
   "HTTP/1.1 400 Bad Request\r\n\r\n",
   "HTTP/1.1 406 Length Required\r\n\r\n",
+  "HTTP/1.1 417 Expectation Failed\r\n\r\n",
   "HTTP/1.1 500 Internal Server Error\r\n\r\n"
 };
 
+static const char *CONTINUE = "HTTP/1.1 100 Continue\r\n\r\n";
+
 enum _rw_state {
   not_yet_done = 1,
   done,
   aborted,
+  expect_continue,
 };
 typedef enum _rw_state read_state;
 typedef enum _rw_state write_state;
@@ -47,12 +54,12 @@
 
 typedef void ev_io_callback(struct ev_loop*, ev_io*, const int);
 
-#if WANT_SIGINT_HANDLING
+#ifdef WANT_SIGINT_HANDLING
 typedef void ev_signal_callback(struct ev_loop*, ev_signal*, const int);
 static ev_signal_callback ev_signal_on_sigint;
 #endif
 
-#if WANT_SIGINT_HANDLING
+#ifdef WANT_SIGNAL_HANDLING
 typedef void ev_timer_callback(struct ev_loop*, ev_timer*, const int);
 static ev_timer_callback ev_timer_ontick;
 ev_timer timeout_watcher;
@@ -75,12 +82,13 @@
 
   ThreadInfo thread_info;
   thread_info.server_info = server_info;
+
   ev_set_userdata(mainloop, &thread_info);
 
   ev_io_init(&thread_info.accept_watcher, ev_io_on_request, 
server_info->sockfd, EV_READ);
   ev_io_start(mainloop, &thread_info.accept_watcher);
 
-#if WANT_SIGINT_HANDLING
+#ifdef WANT_SIGINT_HANDLING
   ev_signal sigint_watcher;
   ev_signal_init(&sigint_watcher, ev_signal_on_sigint, SIGINT);
   ev_signal_start(mainloop, &sigint_watcher);
@@ -99,7 +107,7 @@
   Py_END_ALLOW_THREADS
 }
 
-#if WANT_SIGINT_HANDLING
+#ifdef WANT_SIGINT_HANDLING
 static void
 pyerr_set_interrupt(struct ev_loop* mainloop, struct ev_cleanup* watcher, 
const int events)
 {
@@ -124,7 +132,7 @@
 }
 #endif
 
-#if WANT_SIGNAL_HANDLING
+#ifdef WANT_SIGNAL_HANDLING
 static void
 ev_timer_ontick(struct ev_loop* mainloop, ev_timer* watcher, const int events)
 {
@@ -145,11 +153,13 @@
   client_fd = accept(watcher->fd, (struct sockaddr*)&sockaddr, &addrlen);
   if(client_fd < 0) {
     DBG("Could not accept() client: errno %d", errno);
+    STATSD_INCREMENT("conn.accept.error");
     return;
   }
 
   int flags = fcntl(client_fd, F_GETFL, 0);
   if(fcntl(client_fd, F_SETFL, (flags < 0 ? 0 : flags) | O_NONBLOCK) == -1) {
+    STATSD_INCREMENT("conn.accept.error");
     DBG("Could not set_nonblocking() client %d: errno %d", client_fd, errno);
     return;
   }
@@ -164,6 +174,8 @@
 
   GIL_UNLOCK(0);
 
+  STATSD_INCREMENT("conn.accept.success");
+
   DBG_REQ(request, "Accepted client %s:%d on fd %d",
           inet_ntoa(sockaddr.sin_addr), ntohs(sockaddr.sin_port), client_fd);
 
@@ -172,6 +184,23 @@
   ev_io_start(mainloop, &request->ev_watcher);
 }
 
+
+static void
+start_reading(struct ev_loop *mainloop, Request *request)
+{
+  ev_io_init(&request->ev_watcher, &ev_io_on_read,
+             request->client_fd, EV_READ);
+  ev_io_start(mainloop, &request->ev_watcher);
+}
+
+static void
+start_writing(struct ev_loop *mainloop, Request *request)
+{
+  ev_io_init(&request->ev_watcher, &ev_io_on_write,
+             request->client_fd, EV_WRITE);
+  ev_io_start(mainloop, &request->ev_watcher);
+}
+
 static void
 ev_io_on_read(struct ev_loop* mainloop, ev_io* watcher, const int events)
 {
@@ -192,6 +221,7 @@
     /* Client disconnected */
     read_state = aborted;
     DBG_REQ(request, "Client disconnected");
+    STATSD_INCREMENT("req.error.client_disconnected");
   } else if (read_bytes < 0) {
     /* Would block or error */
     if(errno == EAGAIN || errno == EWOULDBLOCK) {
@@ -199,6 +229,7 @@
     } else {
       read_state = aborted;
       DBG_REQ(request, "Hit errno %d while read()ing", errno);
+      STATSD_INCREMENT("req.error.read");
     }
   } else {
     /* OK, either expect more data or done reading */
@@ -207,14 +238,18 @@
       /* HTTP parse error */
       read_state = done;
       DBG_REQ(request, "Parse error");
+      STATSD_INCREMENT("req.error.parse");
       request->current_chunk = _PEP3333_Bytes_FromString(
         http_error_messages[request->state.error_code]);
       assert(request->iterator == NULL);
     } else if(request->state.parse_finished) {
-      /* HTTP parse successful */
+      /* HTTP parse successful, meaning we have the entire
+       * request (the header _and_ the body). */
       read_state = done;
-      bool wsgi_ok = wsgi_call_application(request);
-      if (!wsgi_ok) {
+
+      STATSD_INCREMENT("req.success.read");
+
+      if (!wsgi_call_application(request)) {
         /* Response is "HTTP 500 Internal Server Error" */
         DBG_REQ(request, "WSGI app error");
         assert(PyErr_Occurred());
@@ -223,7 +258,15 @@
         Py_XCLEAR(request->iterator);
         request->current_chunk = _PEP3333_Bytes_FromString(
           http_error_messages[HTTP_SERVER_ERROR]);
+        STATSD_INCREMENT("req.error.internal");
       }
+    } else if (request->state.expect_continue) {
+      /*
+      ** Handle "Expect: 100-continue" header.
+      ** See https://tools.ietf.org/html/rfc2616#page-48 and `on_header_value`
+      ** in request.c for more details.
+      */
+      read_state = expect_continue;
     } else {
       /* Wait for more data */
       read_state = not_yet_done;
@@ -232,16 +275,23 @@
 
   switch (read_state) {
   case not_yet_done:
+    STATSD_INCREMENT("req.active");
+    break;
+  case expect_continue:
+    DBG_REQ(request, "pause read, write 100-continue");
+    ev_io_stop(mainloop, &request->ev_watcher);
+    request->current_chunk = _PEP3333_Bytes_FromString(CONTINUE);
+    start_writing(mainloop, request);
     break;
   case done:
     DBG_REQ(request, "Stop read watcher, start write watcher");
     ev_io_stop(mainloop, &request->ev_watcher);
-    ev_io_init(&request->ev_watcher, &ev_io_on_write,
-               request->client_fd, EV_WRITE);
-    ev_io_start(mainloop, &request->ev_watcher);
+    start_writing(mainloop, request);
+    STATSD_INCREMENT("req.done");
     break;
   case aborted:
     close_connection(mainloop, request);
+    STATSD_INCREMENT("req.aborted");
     break;
   }
 
@@ -278,27 +328,43 @@
     write_state = on_write_chunk(mainloop, request);
   }
 
+  write_state = request->state.expect_continue
+    ? expect_continue
+    : write_state;
+
   switch(write_state) {
   case not_yet_done:
+    STATSD_INCREMENT("resp.active");
     break;
   case done:
+    STATSD_INCREMENT("resp.done");
     if(request->state.keep_alive) {
       DBG_REQ(request, "done, keep-alive");
+      STATSD_INCREMENT("resp.done.keepalive");
       ev_io_stop(mainloop, &request->ev_watcher);
+
       Request_clean(request);
       Request_reset(request);
-      ev_io_init(&request->ev_watcher, &ev_io_on_read,
-                 request->client_fd, EV_READ);
-      ev_io_start(mainloop, &request->ev_watcher);
+
+      start_reading(mainloop, request);
     } else {
       DBG_REQ(request, "done, close");
+      STATSD_INCREMENT("resp.conn.close");
       close_connection(mainloop, request);
     }
     break;
+  case expect_continue:
+    DBG_REQ(request, "expect continue");
+    ev_io_stop(mainloop, &request->ev_watcher);
+
+    request->state.expect_continue = false;
+    start_reading(mainloop, request);
+    break;
   case aborted:
     /* Response was aborted due to an error. We can't do anything graceful here
      * because at least one chunk is already sent... just close the 
connection. */
     close_connection(mainloop, request);
+    STATSD_INCREMENT("resp.aborted");
     break;
   }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/server.h 
new/bjoern-3.1.0/bjoern/server.h
--- old/bjoern-3.0.1/bjoern/server.h    2015-08-20 12:07:54.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/server.h    2019-10-19 14:58:30.000000000 +0200
@@ -1,11 +1,21 @@
 #ifndef __server_h__
 #define __server_h__
 
+#ifdef WANT_STATSD
+#include "statsd-client.h"
+#endif
+
 typedef struct {
   int sockfd;
   PyObject* wsgi_app;
   PyObject* host;
   PyObject* port;
+#ifdef WANT_STATSD
+  statsd_link* statsd;
+# ifdef WANT_STATSD_TAGS
+  char* statsd_tags;
+# endif
+#endif
 } ServerInfo;
 
 void server_run(ServerInfo*);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/statsd_tags.c 
new/bjoern-3.1.0/bjoern/statsd_tags.c
--- old/bjoern-3.0.1/bjoern/statsd_tags.c       1970-01-01 01:00:00.000000000 
+0100
+++ new/bjoern-3.1.0/bjoern/statsd_tags.c       2019-10-19 14:58:30.000000000 
+0200
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include "statsd-client.h"
+
+#define MAX_MSG_LEN_WITH_TAGS 1000
+
+static int send_stats_with_tags(statsd_link* link, char *stat, size_t value, 
const char* type, const char* tags)
+{
+  if (link == NULL) {
+    // Statsd disabled
+    return 0;
+  }
+
+  char message[MAX_MSG_LEN_WITH_TAGS];
+  snprintf(message, MAX_MSG_LEN_WITH_TAGS, "%s%s:%zd|%s|#%s", link->ns ? 
link->ns : "", stat, value, type, tags ? tags : "");
+  return statsd_send(link, message);
+}
+
+int statsd_inc_with_tags(statsd_link* link, char* stat, const char* tags)
+{
+  return send_stats_with_tags(link, stat, 1, "c", tags);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/statsd_tags.h 
new/bjoern-3.1.0/bjoern/statsd_tags.h
--- old/bjoern-3.0.1/bjoern/statsd_tags.h       1970-01-01 01:00:00.000000000 
+0100
+++ new/bjoern-3.1.0/bjoern/statsd_tags.h       2019-10-19 14:58:30.000000000 
+0200
@@ -0,0 +1,9 @@
+#ifndef __statsd_tags__
+#define __statsd_tags__
+
+#include "statsd-client.h"
+
+int send_stats_with_tags(statsd_link* link, char *stat, size_t value, const 
char *type, const char* tags);
+int statsd_inc_with_tags(statsd_link* link, char *stat, const char* tags);
+
+#endif
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern/wsgi.c 
new/bjoern-3.1.0/bjoern/wsgi.c
--- old/bjoern-3.0.1/bjoern/wsgi.c      2018-08-15 10:12:21.000000000 +0200
+++ new/bjoern-3.1.0/bjoern/wsgi.c      2019-09-12 10:14:48.000000000 +0200
@@ -67,6 +67,7 @@
      _PEP3333_Bytes_Check(PyList_GET_ITEM(retval, 0)))
   {
     /* Optimize the most common case, a single bytestring in a list: */
+    DBG_REQ(request, "WSGI iterable is list of size 1");
     PyObject* tmp = PyList_GET_ITEM(retval, 0);
     Py_INCREF(tmp);
     Py_DECREF(retval);
@@ -77,6 +78,7 @@
      * i.e. sending the response item for item. "item for item" means
      * "char for char" if you have a bytestring. -- I'm not that stupid. */
     bytestring:
+    DBG_REQ(request, "WSGI iterable is byte string");
     request->iterable = NULL;
     request->iterator = NULL;
     if(_PEP3333_Bytes_GET_SIZE(retval)) {
@@ -86,12 +88,14 @@
       Py_DECREF(retval);
       first_chunk = NULL;
     }
-  } else if(FileWrapper_CheckExact(retval) && FileWrapper_GetFd(retval) != -1) 
{
+  } else if(!request->state.response_length_unknown && 
FileWrapper_CheckExact(retval) && FileWrapper_GetFd(retval) != -1) {
+    DBG_REQ(request, "WSGI iterable is wsgi.file_wrapper instance and 
Content-Length is known");
     request->iterable = retval;
     request->iterator = NULL;
     first_chunk = NULL;
   } else {
     /* Generic iterable (list of length != 1, generator, ...) */
+    DBG_REQ(request, "WSGI iterable is some other iterable");
     request->iterable = retval;
     request->iterator = PyObject_GetIter(retval);
     if(request->iterator == NULL)
@@ -125,18 +129,22 @@
     if(request->state.response_length_unknown) {
       if(request->parser.parser.http_major > 0 && 
request->parser.parser.http_minor > 0) {
         /* On HTTP 1.1, we can use Transfer-Encoding: chunked. */
+        DBG_REQ(request, "Content-Length unknown, HTTP/1.1 -> Connection: will 
keep-alive with chunked response");
         request->state.chunked_response = true;
         request->state.keep_alive = true;
       } else {
         /* On HTTP 1.0, we can only resort to closing the connection.  */
+        DBG_REQ(request, "Content-Length unknown, HTTP/1.10 -> will close");
         request->state.keep_alive = false;
       }
     } else {
       /* We know the content-length. Can always keep-alive. */
+        DBG_REQ(request, "Content-Length known -> will keep alive");
       request->state.keep_alive = true;
     }
   } else {
     /* Explicit "Connection: close" (HTTP 1.1) or missing "Connection: 
keep-alive" (HTTP 1.0) */
+    DBG_REQ(request, "Connection: close request by client");
     request->state.keep_alive = false;
   }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern.egg-info/PKG-INFO 
new/bjoern-3.1.0/bjoern.egg-info/PKG-INFO
--- old/bjoern-3.0.1/bjoern.egg-info/PKG-INFO   2019-06-07 12:32:21.000000000 
+0200
+++ new/bjoern-3.1.0/bjoern.egg-info/PKG-INFO   2019-11-03 10:45:13.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bjoern
-Version: 3.0.1
+Version: 3.1.0
 Summary: A screamingly fast Python 2 + 3 WSGI server written in C.
 Home-page: https://github.com/jonashaag/bjoern
 Author: Jonas Haag
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern.egg-info/SOURCES.txt 
new/bjoern-3.1.0/bjoern.egg-info/SOURCES.txt
--- old/bjoern-3.0.1/bjoern.egg-info/SOURCES.txt        2019-06-07 
12:32:21.000000000 +0200
+++ new/bjoern-3.1.0/bjoern.egg-info/SOURCES.txt        2019-11-03 
10:45:13.000000000 +0100
@@ -21,6 +21,8 @@
 bjoern/request.h
 bjoern/server.c
 bjoern/server.h
+bjoern/statsd_tags.c
+bjoern/statsd_tags.h
 bjoern/wsgi.c
 bjoern/wsgi.h
 bjoern.egg-info/PKG-INFO
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/bjoern.py new/bjoern-3.1.0/bjoern.py
--- old/bjoern-3.0.1/bjoern.py  2019-05-27 16:04:07.000000000 +0200
+++ new/bjoern-3.1.0/bjoern.py  2019-11-03 10:43:15.000000000 +0100
@@ -6,7 +6,6 @@
 _default_instance = None
 DEFAULT_LISTEN_BACKLOG = 1024
 
-
 def bind_and_listen(host, port=None, reuse_port=False,
                     listen_backlog=DEFAULT_LISTEN_BACKLOG):
     if host.startswith("unix:@"):
@@ -30,16 +29,25 @@
             sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         sock.bind((host, port))
 
+    sock.setblocking(False)
+
     sock.listen(listen_backlog)
 
     return sock
 
 
-def server_run(sock, wsgi_app):
-    _bjoern.server_run(sock, wsgi_app)
+def server_run(sock, wsgi_app, statsd=None):
+    args = [sock, wsgi_app]
+
+    if _bjoern.features.get('has_statsd'):
+        if statsd:
+            args.extend([int(statsd['enable']), statsd['host'], 
statsd['port'], statsd['ns'], statsd.get('tags')])
+        else:
+            args.extend([0, None, 0, None, None])
+
+    _bjoern.server_run(*args)
 
 
-# Backwards compatibility API
 def listen(wsgi_app, host, port=None, reuse_port=False,
            listen_backlog=DEFAULT_LISTEN_BACKLOG):
     """
@@ -56,6 +64,8 @@
                            listen_backlog=listen_backlog)
     _default_instance = (sock, wsgi_app)
 
+    return sock
+
 
 def run(*args, **kwargs):
     """
@@ -68,6 +78,8 @@
     """
     global _default_instance
 
+    statsd = kwargs.pop('statsd', None)
+
     if args or kwargs:
         # Called as `bjoern.run(wsgi_app, host, ...)`
         listen(*args, **kwargs)
@@ -80,7 +92,7 @@
 
     sock, wsgi_app = _default_instance
     try:
-        server_run(sock, wsgi_app)
+        server_run(sock, wsgi_app, statsd)
     finally:
         if sock.family == socket.AF_UNIX:
             filename = sock.getsockname()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/setup.py new/bjoern-3.1.0/setup.py
--- old/bjoern-3.0.1/setup.py   2019-06-07 12:31:52.000000000 +0200
+++ new/bjoern-3.1.0/setup.py   2019-11-03 10:44:14.000000000 +0100
@@ -2,18 +2,36 @@
 import glob
 from setuptools import setup, Extension
 
+WANT_SIGINT_HANDLING = os.environ.get('BJOERN_WANT_SIGINT_HANDLING', True)
+WANT_SIGNAL_HANDLING = os.environ.get('BJOERN_WANT_SIGNAL_HANDLING', True)
+SIGNAL_CHECK_INTERVAL = os.environ.get('BJOERN_SIGNAL_CHECK_INTERVAL', '0.1')
+WANT_STATSD = os.environ.get('BJOERN_WANT_STATSD', False)
+WANT_STATSD_TAGS = os.environ.get('BJOERN_WANT_STATSD_TAGS', False)
+
+compile_flags = [('SIGNAL_CHECK_INTERVAL', SIGNAL_CHECK_INTERVAL)]
+if WANT_SIGNAL_HANDLING:
+    compile_flags.append(('WANT_SIGNAL_HANDLING', 'yes'))
+if WANT_SIGINT_HANDLING:
+    compile_flags.append(('WANT_SIGINT_HANDLING', 'yes'))
+if WANT_STATSD:
+    compile_flags.append(('WANT_STATSD', 'yes'))
+    if WANT_STATSD_TAGS:
+        compile_flags.append(('WANT_STATSD_TAGS', 'yes'))
+
 SOURCE_FILES = [os.path.join('http-parser', 'http_parser.c')] + \
+               [os.path.join('statsd-c-client', 'statsd-client.c')] + \
                sorted(glob.glob(os.path.join('bjoern', '*.c')))
 
+if not WANT_STATSD:
+    SOURCE_FILES.remove('statsd-c-client/statsd-client.c')
+    SOURCE_FILES.remove('bjoern/statsd_tags.c')
+
 bjoern_extension = Extension(
     '_bjoern',
     sources       = SOURCE_FILES,
     libraries     = ['ev'],
-    include_dirs  = ['http-parser', '/usr/include/libev'],
-    define_macros = [('WANT_SENDFILE', '1'),
-                     ('WANT_SIGINT_HANDLING', '1'),
-                     ('WANT_SIGNAL_HANDLING', '1'),
-                     ('SIGNAL_CHECK_INTERVAL', '0.1')],
+    include_dirs  = ['http-parser', 'statsd-c-client', '/usr/include/libev', 
'/opt/local/include'],
+    define_macros = compile_flags,
     extra_compile_args = ['-std=c99', '-fno-strict-aliasing', '-fcommon',
                           '-fPIC', '-Wall', '-Wextra', '-Wno-unused-parameter',
                           '-Wno-missing-field-initializers', '-g']
@@ -26,7 +44,7 @@
     license      = '2-clause BSD',
     url          = 'https://github.com/jonashaag/bjoern',
     description  = 'A screamingly fast Python 2 + 3 WSGI server written in C.',
-    version      = '3.0.1',
+    version      = '3.1.0',
     classifiers  = ['Development Status :: 4 - Beta',
                     'License :: OSI Approved :: BSD License',
                     'Programming Language :: C',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bjoern-3.0.1/tests/hello.py 
new/bjoern-3.1.0/tests/hello.py
--- old/bjoern-3.0.1/tests/hello.py     2017-08-31 10:28:13.000000000 +0200
+++ new/bjoern-3.1.0/tests/hello.py     2019-10-19 14:58:30.000000000 +0200
@@ -29,4 +29,4 @@
     return choice(apps)(env, start_response)
 
 if __name__ == '__main__':
-    bjoern.run(wsgi_app, '0.0.0.0', 8080)
+    bjoern.run(wsgi_app, '127.0.0.1', 8080)


Reply via email to