Repository: abhinavsingh/proxy.py
Pull request #832 (2021-11-30 22:22:46)

Optimizations & Update Benchmark Results

codecov[bot]: # [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) Report
> Merging [#832](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (23f841f) into [develop](https://codecov.io/gh/abhinavsingh/proxy.py/commit/9782f6ca1e344c5040bd0105fdc804b5995e2361?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (9782f6c) will **decrease** coverage by `0.48%`.
> The diff coverage is `81.25%`.

> :exclamation: Current head 23f841f differs from pull request most recent head 59194fd. Consider uploading reports for the commit 59194fd to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/graphs/tree.svg?width=650&height=150&src=pr&token=Zh9J7b4la2&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)

```diff
@@             Coverage Diff             @@
##           develop     #832      +/-   ##
===========================================
- Coverage    87.41%   86.93%   -0.49%     
===========================================
  Files          128      128              
  Lines         5698     5733      +35     
  Branches       467      473       +6     
===========================================
+ Hits          4981     4984       +3     
- Misses         614      640      +26     
- Partials       103      109       +6     
```

| Flag | Coverage Δ | |
|---|---|---|
| pytest | `86.88% <81.25%> (-0.42%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | Coverage Δ | |
|---|---|---|
| [proxy/core/acceptor/listener.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9hY2NlcHRvci9saXN0ZW5lci5weQ==) | `90.74% <ø> (ø)` | |
| [proxy/core/acceptor/pool.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9hY2NlcHRvci9wb29sLnB5) | `94.23% <ø> (ø)` | |
| [proxy/http/handler.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvaHR0cC9oYW5kbGVyLnB5) | `73.75% <ø> (ø)` | |
| [proxy/plugin/modify\_chunk\_response.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL21vZGlmeV9jaHVua19yZXNwb25zZS5weQ==) | `50.00% <0.00%> (ø)` | |
| [proxy/plugin/modify\_post\_data.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL21vZGlmeV9wb3N0X2RhdGEucHk=) | `89.47% <0.00%> (ø)` | |
| [proxy/plugin/proxy\_pool.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL3Byb3h5X3Bvb2wucHk=) | `32.05% <0.00%> (ø)` | |
| [proxy/plugin/redirect\_to\_custom\_server.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL3JlZGlyZWN0X3RvX2N1c3RvbV9zZXJ2ZXIucHk=) | `85.71% <0.00%> (ø)` | |
| [proxy/core/acceptor/threadless.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9hY2NlcHRvci90aHJlYWRsZXNzLnB5) | `84.11% <50.00%> (-1.86%)` | :arrow_down: |
| [proxy/plugin/web\_server\_route.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL3dlYl9zZXJ2ZXJfcm91dGUucHk=) | `68.00% <50.00%> (+2.78%)` | :arrow_up: |
| [proxy/core/acceptor/acceptor.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9hY2NlcHRvci9hY2NlcHRvci5weQ==) | `77.06% <64.70%> (-0.83%)` | :arrow_down: |
| ... and [12 more](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Last update [9782f6c...59194fd](https://codecov.io/gh/abhinavsingh/proxy.py/pull/832?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).


Patch:
diff --git a/Makefile b/Makefile
index d54ab92..43e520e 100644
--- a/Makefile
+++ b/Makefile
@@ -138,6 +138,7 @@ lib-profile:
 		-o profile.svg \
 		-t -F -s -- \
 		python -m proxy \
+			--hostname 127.0.0.1 \
 			--num-acceptors 1 \
 			--num-workers 1 \
 			--disable-http-proxy \
diff --git a/README.md b/README.md
index 7679895..c0c0cc0 100644
--- a/README.md
+++ b/README.md
@@ -128,63 +128,51 @@
     ```console
     # On Macbook Pro 2019 / 2.4 GHz 8-Core Intel Core i9 / 32 GB RAM
     ❯ ./helper/benchmark.sh
-      CONCURRENCY: 100 workers, TOTAL REQUESTS: 100000 req, QPS: 8000 req/sec, TIMEOUT: 1 sec
+      CONCURRENCY: 100 workers, TOTAL REQUESTS: 100000 req
 
       Summary:
-        Total:	3.1217 secs
-        Slowest:	0.0499 secs
-        Fastest:	0.0004 secs
-        Average:	0.0030 secs
-        Requests/sec:	32033.7261
+        Success rate:	1.0000
+        Total:	2.5489 secs
+        Slowest:	0.0443 secs
+        Fastest:	0.0006 secs
+        Average:	0.0025 secs
+        Requests/sec:	39232.6572
 
-        Total data:	1900000 bytes
-        Size/request:	19 bytes
+        Total data:	1.81 MiB
+        Size/request:	19 B
+        Size/sec:	727.95 KiB
 
       Response time histogram:
-        0.000 [1]	|
-        0.005 [92268]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-        0.010 [7264]	|■■■
-        0.015 [318]	|
-        0.020 [102]	|
-        0.025 [32]	|
-        0.030 [6]	|
-        0.035 [4]	|
-        0.040 [1]	|
-        0.045 [2]	|
-        0.050 [2]	|
-
+        0.001 [5006]  |■■■■■
+        0.001 [19740] |■■■■■■■■■■■■■■■■■■■■■
+        0.002 [29701] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
+        0.002 [21278] |■■■■■■■■■■■■■■■■■■■■■■
+        0.003 [15376] |■■■■■■■■■■■■■■■■
+        0.004 [6644]  |■■■■■■■
+        0.004 [1609]  |■
+        0.005 [434]   |
+        0.006 [83]    |
+        0.006 [29]    |
+        0.007 [100]   |
 
       Latency distribution:
-        10% in 0.0017 secs
-        25% in 0.0020 secs
-        50% in 0.0025 secs
-        75% in 0.0036 secs
-        90% in 0.0050 secs
-        95% in 0.0060 secs
-        99% in 0.0087 secs
+        10% in 0.0014 secs
+        25% in 0.0018 secs
+        50% in 0.0023 secs
+        75% in 0.0030 secs
+        90% in 0.0036 secs
+        95% in 0.0040 secs
+        99% in 0.0047 secs
 
       Details (average, fastest, slowest):
-        DNS+dialup:	0.0000 secs, 0.0004 secs, 0.0499 secs
-        DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-        req write:	0.0000 secs, 0.0000 secs, 0.0020 secs
-        resp wait:	0.0030 secs, 0.0004 secs, 0.0462 secs
-        resp read:	0.0000 secs, 0.0000 secs, 0.0027 secs
+        DNS+dialup:	0.0025 secs, 0.0015 secs, 0.0030 secs
+        DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0001 secs
 
       Status code distribution:
-        [200]	100000 responses
+        [200] 100000 responses
     ```
 
-  - `1 Million` requests benchmark comparison @ `8000 QPS`
-
-    | Server | Throughput (request/sec) |
-    | ------ | ------------ |
-    | `proxy.py` | 30,351 |
-    | `blacksheep` | 7,358 |
-    | `aiohttp` | 6,615 |
-    | `tornado` | 3,301 |
-    | `Flask` | 830 |
-
-    See [Benchmark](https://github.com/abhinavsingh/proxy.py/tree/develop/benchmark#readme) for more details and how to run them locally.
+  - See [Benchmark](https://github.com/abhinavsingh/proxy.py/tree/develop/benchmark#readme) for more details and how to run them locally.
 
 - Lightweight
   - Uses `~5-20 MB` RAM
diff --git a/benchmark/README.md b/benchmark/README.md
index 1f453b0..e22da05 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -4,26 +4,21 @@
 - [TL;DR](#tldr)
 - [Usage](#usage)
 - [Results](#results)
-  - [Proxy.py](#proxypy)
-  - [Blacksheep](#blacksheep)
-  - [AioHttp](#aiohttp)
-  - [Tornado](#tornado)
-  - [Flask](#flask)
 
 ## TL;DR
 
-1 Million requests benchmark results @ 8000 QPS
+NOTE: On Macbook Pro 2019 / 2.4 GHz 8-Core Intel Core i9 / 32 GB RAM
 
-| Server | Throughput (request/sec) |
-| ------ | ------------ |
-| `proxy.py` | 30,351 |
-| `blacksheep` | 7,358 |
-| `aiohttp` | 6,615 |
-| `tornado` | 3,301 |
-| `Flask` | 830 |
+| Server | Throughput (request/sec) | Num Workers | Runner |
+| ------ | ------------ | ------------------------| ------ |
+| `blacksheep` | 46,564 | 10 | uvicorn |
+| `starlette` | 44,102 | 10 | uvicorn |
+| `proxy.py` | 39,232 | 10 | - |
+| `aiohttp` | 6,615 | 1 | - |
+| `tornado` | 3,301 | 1 | - |
 
-- On a single core, `proxy.py` yields `7829 req/sec` throughput
-- Reference: [Add benchmarks comparison for `proxy.py`, `tornado`, `aiohttp`, `flask`](https://github.com/abhinavsingh/proxy.py/pull/827)
+- On a single core, `proxy.py` yields `~9449 req/sec` throughput.
+- Try it using `--num-acceptors=1`
 
 ## Usage
 
@@ -38,290 +33,4 @@
 
 ```console
 ❯ cat /tmp/compare.log
-CONCURRENCY: 100 workers, QPS: 8000 req/sec, TOTAL DURATION: 1m, TIMEOUT: 1 sec
-```
-
-### `Proxy.py`
-
-```console
-=============================
-Benchmarking Proxy.Py
-Server (pid:32232) running
-
-
-Summary:
-  Total:	60.0028 secs
-  Slowest:	0.0932 secs
-  Fastest:	0.0010 secs
-  Average:	0.0060 secs
-  Requests/sec:	30351.6691
-
-  Total data:	34602515 bytes
-  Size/request:	34 bytes
-
-Response time histogram:
-  0.001 [1]	|
-  0.010 [994328]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-  0.019 [5110]	|
-  0.029 [426]	|
-  0.038 [105]	|
-  0.047 [15]	|
-  0.056 [9]	|
-  0.066 [0]	|
-  0.075 [0]	|
-  0.084 [0]	|
-  0.093 [6]	|
-
-
-Latency distribution:
-  10% in 0.0018 secs
-  25% in 0.0022 secs
-  50% in 0.0028 secs
-  75% in 0.0037 secs
-  90% in 0.0050 secs
-  95% in 0.0060 secs
-  99% in 0.0088 secs
-
-Details (average, fastest, slowest):
-  DNS+dialup:	0.0000 secs, 0.0010 secs, 0.0932 secs
-  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-  req write:	0.0000 secs, 0.0000 secs, 0.0071 secs
-  resp wait:	0.0059 secs, 0.0008 secs, 0.0932 secs
-  resp read:	0.0000 secs, 0.0000 secs, 0.0065 secs
-
-Status code distribution:
-  [200]	1000000 responses
-
-Server gracefully shutdown
-=============================
-```
-
-### `Blacksheep`
-
-```console
-=============================
-Benchmarking Blacksheep
-Server (pid:42973) running
-
-Summary:
-  Total:	60.0112 secs
-  Slowest:	0.0357 secs
-  Fastest:	0.0047 secs
-  Average:	0.0136 secs
-  Requests/sec:	7358.7628
-
-  Total data:	8390552 bytes
-  Size/request:	19 bytes
-
-Response time histogram:
-  0.005 [1]	|
-  0.008 [3]	|
-  0.011 [4]	|
-  0.014 [310108]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-  0.017 [123192]	|■■■■■■■■■■■■■■■■
-  0.020 [6795]	|■
-  0.023 [1008]	|
-  0.026 [222]	|
-  0.029 [180]	|
-  0.033 [58]	|
-  0.036 [37]	|
-
-
-Latency distribution:
-  10% in 0.0123 secs
-  25% in 0.0127 secs
-  50% in 0.0133 secs
-  75% in 0.0142 secs
-  90% in 0.0152 secs
-  95% in 0.0158 secs
-  99% in 0.0181 secs
-
-Details (average, fastest, slowest):
-  DNS+dialup:	0.0000 secs, 0.0047 secs, 0.0357 secs
-  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-  req write:	0.0000 secs, 0.0000 secs, 0.0006 secs
-  resp wait:	0.0136 secs, 0.0030 secs, 0.0356 secs
-  resp read:	0.0000 secs, 0.0000 secs, 0.0010 secs
-
-Status code distribution:
-  [200]	441608 responses
-
-
-
-Server gracefully shutdown
-=============================
-```
-
-### `AioHttp`
-
-```console
-=============================
-Benchmarking AIOHTTP
-Server (pid:31148) running
-
-Summary:
-  Total:	60.0098 secs
-  Slowest:	1.4160 secs
-  Fastest:	0.0026 secs
-  Average:	0.0153 secs
-  Requests/sec:	6615.5052
-
-  Total data:	7260812 bytes
-  Size/request:	19 bytes
-
-Response time histogram:
-  0.003 [1]	|
-  0.144 [381427]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-  0.285 [67]	|
-  0.427 [133]	|
-  0.568 [16]	|
-  0.709 [300]	|
-  0.851 [100]	|
-  0.992 [100]	|
-  1.133 [1]	|
-  1.275 [0]	|
-  1.416 [3]	|
-
-
-Latency distribution:
-  10% in 0.0129 secs
-  25% in 0.0133 secs
-  50% in 0.0138 secs
-  75% in 0.0146 secs
-  90% in 0.0155 secs
-  95% in 0.0164 secs
-  99% in 0.0203 secs
-
-Details (average, fastest, slowest):
-  DNS+dialup:	0.0000 secs, 0.0026 secs, 1.4160 secs
-  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-  req write:	0.0000 secs, 0.0000 secs, 0.0006 secs
-  resp wait:	0.0152 secs, 0.0025 secs, 1.4160 secs
-  resp read:	0.0000 secs, 0.0000 secs, 0.9854 secs
-
-Status code distribution:
-  [200]	382148 responses
-
-Error distribution:
-  [96]	Get "http://127.0.0.1:8080/http-route-example": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
-  [14751]	Get "http://127.0.0.1:8080/http-route-example": dial tcp 127.0.0.1:8080: connect: connection refused
-
-Server gracefully shutdown
-=============================
-```
-
-### `Tornado`
-
-```console
-=============================
-Benchmarking Tornado
-Server (pid:31161) running
-
-Summary:
-  Total:	60.0208 secs
-  Slowest:	0.1234 secs
-  Fastest:	0.0050 secs
-  Average:	0.0323 secs
-  Requests/sec:	3301.1874
-
-  Total data:	3515190 bytes
-  Size/request:	19 bytes
-
-Response time histogram:
-  0.005 [1]	|
-  0.017 [6]	|
-  0.029 [13657]	|■■■
-  0.040 [168114]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-  0.052 [2458]	|■
-  0.064 [411]	|
-  0.076 [194]	|
-  0.088 [72]	|
-  0.100 [6]	|
-  0.112 [34]	|
-  0.123 [57]	|
-
-
-Latency distribution:
-  10% in 0.0288 secs
-  25% in 0.0299 secs
-  50% in 0.0317 secs
-  75% in 0.0338 secs
-  90% in 0.0360 secs
-  95% in 0.0378 secs
-  99% in 0.0426 secs
-
-Details (average, fastest, slowest):
-  DNS+dialup:	0.0000 secs, 0.0050 secs, 0.1234 secs
-  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-  req write:	0.0000 secs, 0.0000 secs, 0.0005 secs
-  resp wait:	0.0322 secs, 0.0048 secs, 0.0960 secs
-  resp read:	0.0000 secs, 0.0000 secs, 0.0010 secs
-
-Status code distribution:
-  [200]	185010 responses
-
-Error distribution:
-  [13130]	Get "http://127.0.0.1:8888/http-route-example": dial tcp 127.0.0.1:8888: connect: connection refused
-
-Server gracefully shutdown
-=============================
-```
-
-### `Flask`
-
-```console
-=============================
-Benchmarking Flask
-Server (pid:31127) running
-
-Summary:
-  Total:	60.3213 secs
-  Slowest:	1.0974 secs
-  Fastest:	0.0007 secs
-  Average:	0.0634 secs
-  Requests/sec:	830.1539
-
-  Total data:	637602 bytes
-  Size/request:	19 bytes
-
-Response time histogram:
-  0.001 [1]	|
-  0.110 [32803]	|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-  0.220 [438]	|■
-  0.330 [198]	|
-  0.439 [0]	|
-  0.549 [1]	|
-  0.659 [1]	|
-  0.768 [2]	|
-  0.878 [1]	|
-  0.988 [94]	|
-  1.097 [19]	|
-
-
-Latency distribution:
-  10% in 0.0412 secs
-  25% in 0.0580 secs
-  50% in 0.0637 secs
-  75% in 0.0649 secs
-  90% in 0.0676 secs
-  95% in 0.0716 secs
-  99% in 0.1668 secs
-
-Details (average, fastest, slowest):
-  DNS+dialup:	0.0010 secs, 0.0007 secs, 1.0974 secs
-  DNS-lookup:	0.0000 secs, 0.0000 secs, 0.0000 secs
-  req write:	0.0000 secs, 0.0000 secs, 0.0007 secs
-  resp wait:	0.0620 secs, 0.0005 secs, 1.0967 secs
-  resp read:	0.0003 secs, 0.0000 secs, 0.9407 secs
-
-Status code distribution:
-  [200]	33558 responses
-
-Error distribution:
-  [3359]	Get "http://127.0.0.1:8000/http-route-example": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
-  [12904]	Get "http://127.0.0.1:8000/http-route-example": dial tcp 127.0.0.1:8000: connect: connection refused
-  [255]	Get "http://127.0.0.1:8000/http-route-example": dial tcp 127.0.0.1:8000: i/o timeout (Client.Timeout exceeded while awaiting headers)
-
-Server gracefully shutdown
-=============================
 ```
diff --git a/benchmark/blacksheep/server.py b/benchmark/blacksheep/server.py
index 88ace49..cd966b4 100644
--- a/benchmark/blacksheep/server.py
+++ b/benchmark/blacksheep/server.py
@@ -8,6 +8,8 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
+import uvicorn
+
 from blacksheep.server import Application
 from blacksheep.server.responses import text
 
@@ -18,3 +20,6 @@ app = Application()
 @app.route('/http-route-example')
 async def home(request):    # type: ignore[no-untyped-def]
     return text('HTTP route response')
+
+if __name__ == '__main__':
+    uvicorn.run('server:app', port=9000, workers=10, log_level='warning')
diff --git a/benchmark/compare.sh b/benchmark/compare.sh
index 2a5adcf..d3b5aec 100755
--- a/benchmark/compare.sh
+++ b/benchmark/compare.sh
@@ -31,8 +31,7 @@ if [ $(basename $PWD) != "proxy.py" ]; then
   exit 1
 fi
 
-TIMEOUT=1
-QPS=8000
+TIMEOUT=1sec
 CONCURRENCY=100
 DURATION=1m
 TOTAL_REQUESTS=100000
@@ -41,23 +40,24 @@ BACKLOG=OPEN_FILE_LIMIT
 
 SERVER_HOST=127.0.0.1
 
-FLASK_PORT=8000
 AIOHTTP_PORT=8080
 TORNADO_PORT=8888
+STARLETTE_PORT=8890
 PROXYPY_PORT=8899
 BLACKSHEEP_PORT=9000
 
 ulimit -n $OPEN_FILE_LIMIT
 
-echo "CONCURRENCY: $CONCURRENCY workers, QPS: $QPS req/sec, TOTAL DURATION: $DURATION, TIMEOUT: $TIMEOUT sec"
+echo "CONCURRENCY: $CONCURRENCY workers, DURATION: $DURATION, TIMEOUT: $TIMEOUT"
 
 run_benchmark() {
-  hey \
-      -z $DURATION \
-      -c $CONCURRENCY \
-      -q $QPS \
-      -t $TIMEOUT \
-      http://127.0.0.1:$1/http-route-example
+  oha \
+    --no-tui \
+    --latency-correction \
+    -z $DURATION \
+    -c $CONCURRENCY \
+    -t $TIMEOUT \
+    http://127.0.0.1:$1/http-route-example
 }
 
 benchmark_lib() {
@@ -84,7 +84,9 @@ benchmark_proxy_py() {
     --enable-web-server \
     --plugin proxy.plugin.WebServerPlugin \
     --disable-http-proxy \
-    --local-executor --log-file /dev/null > /dev/null 2>&1 &
+    --num-acceptors 1 \
+    --local-executor \
+    --log-file /dev/null > /dev/null 2>&1 &
   local SERVER_PID=$!
   echo "Server (pid:$SERVER_PID) running"
   sleep 1
@@ -116,27 +118,27 @@ benchmark_asgi() {
   fi
 }
 
-echo "============================="
-echo "Benchmarking Proxy.Py"
-benchmark_proxy_py $PROXYPY_PORT
-echo "============================="
-
-echo "============================="
-echo "Benchmarking Blacksheep"
-benchmark_asgi $BLACKSHEEP_PORT benchmark.blacksheep.server:app
-echo "============================="
-
-echo "============================="
-echo "Benchmarking AIOHTTP"
-benchmark_lib aiohttp $AIOHTTP_PORT
-echo "============================="
-
-echo "============================="
-echo "Benchmarking Tornado"
-benchmark_lib tornado $TORNADO_PORT
-echo "============================="
-
-echo "============================="
-echo "Benchmarking Flask"
-benchmark_lib flask $FLASK_PORT
-echo "============================="
+# echo "============================="
+# echo "Benchmarking Proxy.Py"
+# benchmark_proxy_py $PROXYPY_PORT
+# echo "============================="
+
+# echo "============================="
+# echo "Benchmarking Blacksheep"
+# benchmark_lib blacksheep $BLACKSHEEP_PORT
+# echo "============================="
+
+# echo "============================="
+# echo "Benchmarking Starlette"
+# benchmark_lib starlette $STARLETTE_PORT
+# echo "============================="
+
+# echo "============================="
+# echo "Benchmarking AIOHTTP"
+# benchmark_lib aiohttp $AIOHTTP_PORT
+# echo "============================="
+
+# echo "============================="
+# echo "Benchmarking Tornado"
+# benchmark_lib tornado $TORNADO_PORT
+# echo "============================="
diff --git a/benchmark/requirements.txt b/benchmark/requirements.txt
index 2429a40..1241908 100644
--- a/benchmark/requirements.txt
+++ b/benchmark/requirements.txt
@@ -1,5 +1,5 @@
 aiohttp==3.8.1
 blacksheep==1.2.1
-Flask==2.0.2
+starlette==0.17.1
 tornado==6.1
 uvicorn==0.15.0
diff --git a/benchmark/flask/__init__.py b/benchmark/starlette/__init__.py
similarity index 100%
rename from benchmark/flask/__init__.py
rename to benchmark/starlette/__init__.py
diff --git a/benchmark/starlette/server.py b/benchmark/starlette/server.py
new file mode 100644
index 0000000..0e27e02
--- /dev/null
+++ b/benchmark/starlette/server.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+"""
+    proxy.py
+    ~~~~~~~~
+    ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
+    Network monitoring, controls & Application development, testing, debugging.
+
+    :copyright: (c) 2013-present by Abhinav Singh and contributors.
+    :license: BSD, see LICENSE for more details.
+"""
+import uvicorn
+
+from starlette.applications import Starlette
+from starlette.responses import Response
+from starlette.routing import Route
+
+
+async def homepage(request):    # type: ignore[no-untyped-def]
+    return Response('HTTP route response', media_type='text/plain')
+
+
+app = Starlette(
+    debug=True, routes=[
+        Route('/http-route-example', homepage),
+    ],
+)
+
+
+if __name__ == '__main__':
+    uvicorn.run('server:app', port=8890, workers=10, log_level='warning')
diff --git a/benchmark/flask/server.py b/benchmark/tornado/__init__.py
similarity index 60%
rename from benchmark/flask/server.py
rename to benchmark/tornado/__init__.py
index a18324c..232621f 100644
--- a/benchmark/flask/server.py
+++ b/benchmark/tornado/__init__.py
@@ -8,15 +8,3 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
-from flask import Flask
-
-app = Flask(__name__)
-
-
-@app.route('/http-route-example')   # type: ignore
-def hello_world() -> str:
-    return 'HTTP route response'
-
-
-if __name__ == '__main__':
-    app.run(host='127.0.0.1', port=8000)
diff --git a/examples/https_connect_tunnel.py b/examples/https_connect_tunnel.py
index e60edae..b138b7b 100644
--- a/examples/https_connect_tunnel.py
+++ b/examples/https_connect_tunnel.py
@@ -50,7 +50,7 @@ class HttpsConnectTunnelHandler(BaseTcpTunnelHandler):
         self.request.parse(data)
 
         # Drop the request if not a CONNECT request
-        if not self.request.is_https_tunnel():
+        if not self.request.is_https_tunnel:
             self.work.queue(
                 HttpsConnectTunnelHandler.PROXY_TUNNEL_UNSUPPORTED_SCHEME,
             )
diff --git a/helper/benchmark.sh b/helper/benchmark.sh
index aaa6232..ca0cc43 100755
--- a/helper/benchmark.sh
+++ b/helper/benchmark.sh
@@ -31,8 +31,7 @@ if [ $(basename $PWD) != "proxy.py" ]; then
   exit 1
 fi
 
-TIMEOUT=1
-QPS=8000
+TIMEOUT=1sec
 CONCURRENCY=100
 TOTAL_REQUESTS=100000
 OPEN_FILE_LIMIT=65536
@@ -50,13 +49,18 @@ ADDR=$(lsof -Pan -p $PID -i | grep -v COMMAND | awk '{ print $9 }')
 
 PRE_RUN_OPEN_FILES=$(./helper/monitor_open_files.sh)
 
-echo "CONCURRENCY: $CONCURRENCY workers, TOTAL REQUESTS: $TOTAL_REQUESTS req, QPS: $QPS req/sec, TIMEOUT: $TIMEOUT sec"
-hey \
+run_benchmark() {
+  echo "CONCURRENCY: $CONCURRENCY workers, TOTAL REQUESTS: $TOTAL_REQUESTS req"
+  oha \
+    --no-tui \
+    --latency-correction \
     -n $TOTAL_REQUESTS \
     -c $CONCURRENCY \
-    -q $QPS \
     -t $TIMEOUT \
     http://$ADDR/http-route-example
+}
+
+run_benchmark
 
 POST_RUN_OPEN_FILES=$(./helper/monitor_open_files.sh)
 
diff --git a/proxy/common/backports.py b/proxy/common/backports.py
index b6f5dfd..9aec16d 100644
--- a/proxy/common/backports.py
+++ b/proxy/common/backports.py
@@ -38,8 +38,7 @@ class cached_property:
     two-element tuple with the last computed property value and the last time
     it was updated in seconds since the epoch.
 
-    The default time-to-live (TTL) is 300 seconds (5 minutes). Set the TTL to
-    zero for the cached value to never expire.
+    The default time-to-live (TTL) is 0 seconds i.e. cached value will never expire.
 
     To expire a cached property value manually just do::
         del instance._cached_properties[<property name>]
@@ -58,7 +57,7 @@ class cached_property:
        Arndt
     """
 
-    def __init__(self, ttl: float = 300.0):
+    def __init__(self, ttl: float = 0):
         self.ttl = ttl
 
     def __call__(self, fget: Any, doc: Any = None) -> 'cached_property':
diff --git a/proxy/common/constants.py b/proxy/common/constants.py
index c941676..b2e4f6d 100644
--- a/proxy/common/constants.py
+++ b/proxy/common/constants.py
@@ -111,6 +111,7 @@ DEFAULT_ENABLE_PROXY_PROTOCOL = False
 # 25 milliseconds to keep the loops hot
 # Will consume ~0.3-0.6% CPU when idle.
 DEFAULT_SELECTOR_SELECT_TIMEOUT = 25 / 1000
+DEFAULT_WAIT_FOR_TASKS_TIMEOUT = 1 / 1000
 DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT = 1   # in seconds
 
 DEFAULT_DEVTOOLS_DOC_URL = 'http://proxy'
diff --git a/proxy/core/acceptor/acceptor.py b/proxy/core/acceptor/acceptor.py
index 05cfa09..1d08584 100644
--- a/proxy/core/acceptor/acceptor.py
+++ b/proxy/core/acceptor/acceptor.py
@@ -75,6 +75,7 @@ class Acceptor(multiprocessing.Process):
             fd_queue: connection.Connection,
             flags: argparse.Namespace,
             lock: multiprocessing.synchronize.Lock,
+            # semaphore: multiprocessing.synchronize.Semaphore,
             executor_queues: List[connection.Connection],
             executor_pids: List[int],
             executor_locks: List[multiprocessing.synchronize.Lock],
@@ -88,6 +89,7 @@ class Acceptor(multiprocessing.Process):
         self.idd = idd
         # Mutex used for synchronization with acceptors
         self.lock = lock
+        # self.semaphore = semaphore
         # Queue over which server socket fd is received on start-up
         self.fd_queue: connection.Connection = fd_queue
         # Available executors
@@ -106,36 +108,54 @@ class Acceptor(multiprocessing.Process):
         self._local: Optional[LocalExecutor] = None
         self._lthread: Optional[threading.Thread] = None
 
-    def accept(self, events: List[Tuple[selectors.SelectorKey, int]]) -> None:
+    def accept(
+            self,
+            events: List[Tuple[selectors.SelectorKey, int]],
+    ) -> List[Tuple[socket.socket, Optional[Tuple[str, int]]]]:
+        works = []
         for _, mask in events:
             if mask & selectors.EVENT_READ:
                 if self.sock is not None:
-                    conn, addr = self.sock.accept()
-                    logging.debug(
-                        'Accepting new work#{0}'.format(conn.fileno()),
-                    )
-                    work = (conn, addr or None)
-                    if self.flags.local_executor:
-                        assert self._local_work_queue
-                        self._local_work_queue.put(work)
-                    else:
-                        self._work(*work)
+                    try:
+                        conn, addr = self.sock.accept()
+                        logging.debug(
+                            'Accepting new work#{0}'.format(conn.fileno()),
+                        )
+                        works.append((conn, addr or None))
+                    except BlockingIOError:
+                        # logger.info('blocking io error')
+                        pass
+        return works
 
     def run_once(self) -> None:
         if self.selector is not None:
             events = self.selector.select(timeout=1)
             if len(events) == 0:
                 return
-            locked = False
+            # locked = False
+            # try:
+            #     if self.lock.acquire(block=False):
+            #         locked = True
+            #         self.semaphore.release()
+            # finally:
+            #     if locked:
+            #         self.lock.release()
+            locked, works = False, []
             try:
+                # if not self.semaphore.acquire(False, None):
+                #     return
                 if self.lock.acquire(block=False):
                     locked = True
-                    self.accept(events)
-            except BlockingIOError:
-                pass
+                    works = self.accept(events)
             finally:
                 if locked:
                     self.lock.release()
+            for work in works:
+                if self.flags.local_executor:
+                    assert self._local_work_queue
+                    self._local_work_queue.put(work)
+                else:
+                    self._work(*work)
 
     def run(self) -> None:
         Logger.setup(
diff --git a/proxy/core/acceptor/listener.py b/proxy/core/acceptor/listener.py
index bef4b44..ac3b211 100644
--- a/proxy/core/acceptor/listener.py
+++ b/proxy/core/acceptor/listener.py
@@ -108,6 +108,7 @@ class Listener:
         self._socket = socket.socket(self.flags.family, socket.SOCK_STREAM)
         self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        # self._socket.setsockopt(socket.SOL_TCP, socket.TCP_FASTOPEN, 5)
         self._socket.bind((str(self.flags.hostname), self.flags.port))
         self._socket.listen(self.flags.backlog)
         self._socket.setblocking(False)
diff --git a/proxy/core/acceptor/pool.py b/proxy/core/acceptor/pool.py
index dd110d3..90e7c77 100644
--- a/proxy/core/acceptor/pool.py
+++ b/proxy/core/acceptor/pool.py
@@ -86,6 +86,7 @@ class AcceptorPool:
         self.fd_queues: List[connection.Connection] = []
         # Internals
         self.lock = multiprocessing.Lock()
+        # self.semaphore = multiprocessing.Semaphore(0)
 
     def __enter__(self) -> 'AcceptorPool':
         self.setup()
@@ -125,6 +126,7 @@ class AcceptorPool:
                 fd_queue=work_queue[1],
                 flags=self.flags,
                 lock=self.lock,
+                # semaphore=self.semaphore,
                 event_queue=self.event_queue,
                 executor_queues=self.executor_queues,
                 executor_pids=self.executor_pids,
diff --git a/proxy/core/acceptor/threadless.py b/proxy/core/acceptor/threadless.py
index cc9292c..5a0b325 100644
--- a/proxy/core/acceptor/threadless.py
+++ b/proxy/core/acceptor/threadless.py
@@ -27,6 +27,7 @@ from typing import Dict, Optional, Tuple, List, Set, Generic, TypeVar, Union
 from ...common.logger import Logger
 from ...common.types import Readables, Writables
 from ...common.constants import DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT, DEFAULT_SELECTOR_SELECT_TIMEOUT
+from ...common.constants import DEFAULT_WAIT_FOR_TASKS_TIMEOUT
 
 from ..connection import TcpClientConnection
 from ..event import eventNames, EventQueue
@@ -85,7 +86,7 @@ class Threadless(ABC, Generic[T]):
             # fileno, mask
             Dict[int, int],
         ] = {}
-        self.wait_timeout: float = DEFAULT_SELECTOR_SELECT_TIMEOUT
+        self.wait_timeout: float = DEFAULT_WAIT_FOR_TASKS_TIMEOUT
         self.cleanup_inactive_timeout: float = DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT
 
     @property
@@ -128,6 +129,7 @@ class Threadless(ABC, Generic[T]):
             ),
             flags=self.flags,
             event_queue=self.event_queue,
+            uid=fileno,
         )
         self.works[fileno].publish_event(
             event_name=eventNames.WORK_STARTED,
@@ -150,7 +152,12 @@ class Threadless(ABC, Generic[T]):
         """For each work, collects events they are interested in.
         Calls select for events of interest.  """
         assert self.selector is not None
+        unfinished_work_ids = set()
+        for task in self.unfinished:
+            unfinished_work_ids.add(task._work_id)   # type: ignore
         for work_id in self.works:
+            if work_id in unfinished_work_ids:
+                continue
             worker_events = await self.works[work_id].get_events()
             # NOTE: Current assumption is that multiple works will not
             # be interested in the same fd.  Descriptors of interests
@@ -225,7 +232,7 @@ class Threadless(ABC, Generic[T]):
         )
         for task in finished:
             if task.result():
-                self._cleanup(task._work_id)     # type: ignore
+                self._cleanup(task._work_id)    # type: ignore
                 # self.cleanup(int(task.get_name()))
 
     def _fromfd(self, fileno: int) -> socket.socket:
diff --git a/proxy/http/handler.py b/proxy/http/handler.py
index 91c9662..1256def 100644
--- a/proxy/http/handler.py
+++ b/proxy/http/handler.py
@@ -211,6 +211,7 @@ class HttpProtocolHandler(BaseTcpServerHandler):
             # valid request.
             if data and self.request.state != httpParserStates.COMPLETE:
                 # Parse http request
+                #
                 # TODO(abhinavsingh): Remove .tobytes after parser is
                 # memoryview compliant
                 self.request.parse(data.tobytes())
diff --git a/proxy/http/parser/parser.py b/proxy/http/parser/parser.py
index cff81ab..9df644f 100644
--- a/proxy/http/parser/parser.py
+++ b/proxy/http/parser/parser.py
@@ -83,6 +83,10 @@ class HttpParser:
         self.chunk: Optional[ChunkParser] = None
         # Internal request line as a url structure
         self._url: Optional[Url] = None
+        # Deduced states from the packet
+        self._is_chunked_encoded: bool = False
+        self._content_expected: bool = False
+        self._is_https_tunnel: bool = False
 
     @classmethod
     def request(
@@ -113,9 +117,13 @@ class HttpParser:
         """Returns true if header key was found in payload."""
         return key.lower() in self.headers
 
-    def add_header(self, key: bytes, value: bytes) -> None:
-        """Add/Update a header to internal data structure."""
-        self.headers[key.lower()] = (key, value)
+    def add_header(self, key: bytes, value: bytes) -> bytes:
+        """Add/Update a header to internal data structure.
+
+        Returns key with which passed (key, value) tuple is available."""
+        k = key.lower()
+        self.headers[k] = (key, value)
+        return k
 
     def add_headers(self, headers: List[Tuple[bytes, bytes]]) -> None:
         """Add/Update multiple headers to internal data structure"""
@@ -143,6 +151,7 @@ class HttpParser:
         NOTE: Host field WILL be None for incoming local WebServer requests."""
         return self.host is not None
 
+    @property
     def is_http_1_1_keep_alive(self) -> bool:
         """Returns true for HTTP/1.1 keep-alive connections."""
         return self.version == HTTP_1_1 and \
@@ -151,28 +160,32 @@ class HttpParser:
                 self.header(b'Connection').lower() == b'keep-alive'
             )
 
+    @property
     def is_connection_upgrade(self) -> bool:
         """Returns true for websocket upgrade requests."""
         return self.version == HTTP_1_1 and \
             self.has_header(b'Connection') and \
             self.has_header(b'Upgrade')
 
+    @property
     def is_https_tunnel(self) -> bool:
         """Returns true for HTTPS CONNECT tunnel request."""
-        return self.method == httpMethods.CONNECT
+        return self._is_https_tunnel
 
+    @property
     def is_chunked_encoded(self) -> bool:
         """Returns true if transfer-encoding chunked is used."""
-        return b'transfer-encoding' in self.headers and \
-               self.headers[b'transfer-encoding'][1].lower() == b'chunked'
+        return self._is_chunked_encoded
 
+    @property
     def content_expected(self) -> bool:
         """Returns true if content-length is present and not 0."""
-        return b'content-length' in self.headers and int(self.header(b'content-length')) > 0
+        return self._content_expected
 
+    @property
     def body_expected(self) -> bool:
         """Returns true if content or chunked response is expected."""
-        return self.content_expected() or self.is_chunked_encoded()
+        return self.content_expected or self.is_chunked_encoded
 
     def parse(self, raw: bytes) -> None:
         """Parses HTTP request out of raw bytes.
@@ -186,6 +199,18 @@ class HttpParser:
             more, raw = self._process_body(raw) \
                 if self.state >= httpParserStates.HEADERS_COMPLETE else \
                 self._process_line_and_headers(raw)
+            # When server sends a response line without any header or body e.g.
+            # HTTP/1.1 200 Connection established\r\n\r\n
+            if self.state == httpParserStates.LINE_RCVD and \
+                    raw == CRLF and \
+                    self.type == httpParserTypes.RESPONSE_PARSER:
+                self.state = httpParserStates.COMPLETE
+            # Mark request as complete if headers received and no incoming
+            # body indication received.
+            elif self.state == httpParserStates.HEADERS_COMPLETE and \
+                    not self.body_expected and \
+                    raw == b'':
+                self.state = httpParserStates.COMPLETE
         self.buffer = raw
 
     def build(self, disable_headers: Optional[List[bytes]] = None, for_proxy: bool = False) -> bytes:
@@ -204,7 +229,7 @@ class HttpParser:
                 COLON +
                 str(self.port).encode() +
                 path
-            ) if not self.is_https_tunnel() else (self.host + COLON + str(self.port).encode())
+            ) if not self.is_https_tunnel else (self.host + COLON + str(self.port).encode())
         return build_http_request(
             self.method, path, self.version,
             headers={} if not self.headers else {
@@ -238,7 +263,7 @@ class HttpParser:
         #   the latter MUST be ignored.
         #
         # TL;DR -- Give transfer-encoding header preference over content-length.
-        if self.is_chunked_encoded():
+        if self.is_chunked_encoded:
             if not self.chunk:
                 self.chunk = ChunkParser()
             raw = self.chunk.parse(raw)
@@ -271,35 +296,37 @@ class HttpParser:
         return more, raw
 
     def _process_line_and_headers(self, raw: bytes) -> Tuple[bool, bytes]:
-        """Returns False when no CRLF could be found in received bytes."""
-        line, raw = find_http_line(raw)
-        if line is None:
-            return False, raw
+        """Returns False when no CRLF could be found in received bytes.
+
+        TODO: We should not return until parser reaches headers complete
+        state or when there is no more data left to parse.
+
+        TODO: For protection against Slowloris attack, we must parse the
+        request line and headers only after receiving end of header marker.
+        This will also help make the parser even more stateless.
+        """
+        while True:
+            line, raw = find_http_line(raw)
+            if line is None:
+                return False, raw
 
-        if self.state == httpParserStates.INITIALIZED:
-            self._process_line(line)
             if self.state == httpParserStates.INITIALIZED:
-                return len(raw) > 0, raw
-        elif self.state in (httpParserStates.LINE_RCVD, httpParserStates.RCVING_HEADERS):
-            if self.state == httpParserStates.LINE_RCVD:
-                # LINE_RCVD state is equivalent to RCVING_HEADERS
-                self.state = httpParserStates.RCVING_HEADERS
-            if line.strip() == b'':  # Blank line received.
-                self.state = httpParserStates.HEADERS_COMPLETE
-            else:
-                self._process_header(line)
-
-        # When server sends a response line without any header or body e.g.
-        # HTTP/1.1 200 Connection established\r\n\r\n
-        if self.state == httpParserStates.LINE_RCVD and \
-                self.type == httpParserTypes.RESPONSE_PARSER and \
-                raw == CRLF:
-            self.state = httpParserStates.COMPLETE
-        elif self.state == httpParserStates.HEADERS_COMPLETE and \
-                not self.body_expected() and \
-                raw == b'':
-            self.state = httpParserStates.COMPLETE
+                self._process_line(line)
+                if self.state == httpParserStates.INITIALIZED:
+                    # return len(raw) > 0, raw
+                    continue
+            elif self.state in (httpParserStates.LINE_RCVD, httpParserStates.RCVING_HEADERS):
+                if self.state == httpParserStates.LINE_RCVD:
+                    self.state = httpParserStates.RCVING_HEADERS
+                if line == b'' or line.strip() == b'':  # Blank line received.
+                    self.state = httpParserStates.HEADERS_COMPLETE
+                else:
+                    self._process_header(line)
 
+            # If raw length is now zero, bail out
+            # If we have received all headers, bail out
+            if raw == b'' or self.state == httpParserStates.HEADERS_COMPLETE:
+                break
         return len(raw) > 0, raw
 
     def _process_line(self, raw: bytes) -> None:
@@ -310,9 +337,11 @@ class HttpParser:
                 self.protocol.parse(raw)
             else:
                 # Ref: https://datatracker.ietf.org/doc/html/rfc2616#section-5.1
-                line = raw.split(WHITESPACE)
+                line = raw.split(WHITESPACE, 2)
                 if len(line) == 3:
                     self.method = line[0].upper()
+                    if self.method == httpMethods.CONNECT:
+                        self._is_https_tunnel = True
                     self.set_url(line[1])
                     self.version = line[2]
                     self.state = httpParserStates.LINE_RCVD
@@ -324,26 +353,35 @@ class HttpParser:
                     # but we should solve circular import problem first.
                     raise ValueError('Invalid request line')
         else:
-            line = raw.split(WHITESPACE)
+            line = raw.split(WHITESPACE, 2)
             self.version = line[0]
             self.code = line[1]
-            self.reason = WHITESPACE.join(line[2:])
+            self.reason = line[2]
             self.state = httpParserStates.LINE_RCVD
 
     def _process_header(self, raw: bytes) -> None:
-        parts = raw.split(COLON)
-        key = parts[0].strip()
-        value = COLON.join(parts[1:]).strip()
-        self.add_headers([(key, value)])
+        parts = raw.split(COLON, 1)
+        key, value = (
+            parts[0].strip(),
+            b'' if len(parts) == 1 else parts[1].strip(),
+        )
+        k = self.add_header(key, value)
+        # b'content-length' in self.headers and int(self.header(b'content-length')) > 0
+        if k == b'content-length' and int(value) > 0:
+            self._content_expected = True
+        # return b'transfer-encoding' in self.headers and \
+        #   self.headers[b'transfer-encoding'][1].lower() == b'chunked'
+        elif k == b'transfer-encoding' and value.lower() == b'chunked':
+            self._is_chunked_encoded = True
 
     def _get_body_or_chunks(self) -> Optional[bytes]:
         return ChunkParser.to_chunks(self.body) \
-            if self.body and self.is_chunked_encoded() else \
+            if self.body and self.is_chunked_encoded else \
             self.body
 
     def _set_line_attributes(self) -> None:
         if self.type == httpParserTypes.REQUEST_PARSER:
-            if self.is_https_tunnel() and self._url:
+            if self.is_https_tunnel and self._url:
                 self.host = self._url.hostname
                 self.port = 443 if self._url.port is None else self._url.port
             elif self._url:
diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py
index c9f01d1..2dafc43 100644
--- a/proxy/http/proxy/server.py
+++ b/proxy/http/proxy/server.py
@@ -307,7 +307,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
             # parse incoming response packet
             # only for non-https requests and when
             # tls interception is enabled
-            if not self.request.is_https_tunnel():
+            if not self.request.is_https_tunnel:
                 # See https://github.com/abhinavsingh/proxy.py/issues/127 for why
                 # currently response parsing is disabled when TLS interception is enabled.
                 #
@@ -425,7 +425,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
 
     def access_log(self, log_attrs: Dict[str, Any]) -> None:
         access_log_format = DEFAULT_HTTPS_ACCESS_LOG_FORMAT
-        if not self.request.is_https_tunnel():
+        if not self.request.is_https_tunnel:
             access_log_format = DEFAULT_HTTP_ACCESS_LOG_FORMAT
         logger.info(access_log_format.format_map(log_attrs))
 
@@ -435,7 +435,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
         # However, this must also be accompanied by resetting both request
         # and response objects.
         #
-        # if not self.request.is_https_tunnel() and \
+        # if not self.request.is_https_tunnel and \
         #         self.response.state == httpParserStates.COMPLETE:
         #     self.access_log()
         return chunk
@@ -466,11 +466,11 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
             # We also handle pipeline scenario for https proxy
             # requests is TLS interception is enabled.
             if self.request.state == httpParserStates.COMPLETE and (
-                    not self.request.is_https_tunnel() or
+                    not self.request.is_https_tunnel or
                     self.tls_interception_enabled()
             ):
                 if self.pipeline_request is not None and \
-                        self.pipeline_request.is_connection_upgrade():
+                        self.pipeline_request.is_connection_upgrade:
                     # Previous pipelined request was a WebSocket
                     # upgrade request. Incoming client data now
                     # must be treated as WebSocket protocol packets.
@@ -503,7 +503,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
                             self.pipeline_request.build(),
                         ),
                     )
-                    if not self.pipeline_request.is_connection_upgrade():
+                    if not self.pipeline_request.is_connection_upgrade:
                         self.pipeline_request = None
             # For scenarios where we cannot peek into the data,
             # simply queue for upstream server.
@@ -549,7 +549,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
         # For https requests, respond back with tunnel established response.
         # Optionally, setup interceptor if TLS interception is enabled.
         if self.upstream:
-            if self.request.is_https_tunnel():
+            if self.request.is_https_tunnel:
                 self.client.queue(
                     HttpProxyPlugin.PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT,
                 )
@@ -910,7 +910,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
             event_name=eventNames.REQUEST_COMPLETE,
             event_payload={
                 'url': text_(self.request.path)
-                if self.request.is_https_tunnel()
+                if self.request.is_https_tunnel
                 else 'http://%s:%d%s' % (text_(self.request.host), self.request.port, text_(self.request.path)),
                 'method': text_(self.request.method),
                 'headers': {text_(k): text_(v[1]) for k, v in self.request.headers.items()},
diff --git a/proxy/http/server/web.py b/proxy/http/server/web.py
index 129c088..b032d0a 100644
--- a/proxy/http/server/web.py
+++ b/proxy/http/server/web.py
@@ -25,7 +25,6 @@ from ...common.constants import DEFAULT_STATIC_SERVER_DIR, PROXY_AGENT_HEADER_VA
 from ...common.constants import DEFAULT_ENABLE_STATIC_SERVER, DEFAULT_ENABLE_WEB_SERVER
 from ...common.constants import DEFAULT_MIN_COMPRESSION_LIMIT, DEFAULT_WEB_ACCESS_LOG_FORMAT
 from ...common.utils import bytes_, text_, build_http_response, build_websocket_handshake_response
-from ...common.backports import cached_property
 from ...common.types import Readables, Writables
 from ...common.flag import flags
 
@@ -114,27 +113,28 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
         self.route: Optional[HttpWebServerBasePlugin] = None
 
         self.plugins: Dict[str, HttpWebServerBasePlugin] = {}
-        if b'HttpWebServerBasePlugin' in self.flags.plugins:
-            for klass in self.flags.plugins[b'HttpWebServerBasePlugin']:
-                instance: HttpWebServerBasePlugin = klass(
-                    self.uid,
-                    self.flags,
-                    self.client,
-                    self.event_queue,
-                )
-                self.plugins[instance.name()] = instance
-
-    @cached_property(ttl=0)
-    def routes(self) -> Dict[int, Dict[Pattern[str], HttpWebServerBasePlugin]]:
-        r: Dict[int, Dict[Pattern[str], HttpWebServerBasePlugin]] = {
+        self.routes: Dict[
+            int, Dict[Pattern[str], HttpWebServerBasePlugin],
+        ] = {
             httpProtocolTypes.HTTP: {},
             httpProtocolTypes.HTTPS: {},
             httpProtocolTypes.WEBSOCKET: {},
         }
-        for name in self.plugins:
-            for (protocol, route) in self.plugins[name].routes():
-                r[protocol][re.compile(route)] = self.plugins[name]
-        return r
+        if b'HttpWebServerBasePlugin' in self.flags.plugins:
+            self._initialize_web_plugins()
+
+    def _initialize_web_plugins(self) -> None:
+        for klass in self.flags.plugins[b'HttpWebServerBasePlugin']:
+            instance: HttpWebServerBasePlugin = klass(
+                self.uid,
+                self.flags,
+                self.client,
+                self.event_queue,
+            )
+            self.plugins[instance.name()] = instance
+            for (protocol, route) in instance.routes():
+                pattern = re.compile(route)
+                self.routes[protocol][pattern] = self.plugins[instance.name()]
 
     def encryption_enabled(self) -> bool:
         return self.flags.keyfile is not None and \
@@ -223,7 +223,7 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
                 break
         # No-route found, try static serving if enabled
         if self.flags.enable_static_server:
-            path = text_(path).split('?')[0]
+            path = text_(path).split('?', 1)[0]
             self.client.queue(
                 self.read_and_build_static_file_response(
                     self.flags.static_server_dir + path,
@@ -279,7 +279,7 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
         # If 1st valid request was completed and it's a HTTP/1.1 keep-alive
         # And only if we have a route, parse pipeline requests
         if self.request.state == httpParserStates.COMPLETE and \
-                self.request.is_http_1_1_keep_alive() and \
+                self.request.is_http_1_1_keep_alive and \
                 self.route is not None:
             if self.pipeline_request is None:
                 self.pipeline_request = HttpParser(
@@ -290,7 +290,7 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
             self.pipeline_request.parse(raw.tobytes())
             if self.pipeline_request.state == httpParserStates.COMPLETE:
                 self.route.handle_request(self.pipeline_request)
-                if not self.pipeline_request.is_http_1_1_keep_alive():
+                if not self.pipeline_request.is_http_1_1_keep_alive:
                     logger.error(
                         'Pipelined request is not keep-alive, will tear down request...',
                     )
diff --git a/proxy/http/url.py b/proxy/http/url.py
index 2d50743..177e382 100644
--- a/proxy/http/url.py
+++ b/proxy/http/url.py
@@ -73,14 +73,14 @@ class Url:
             rest = raw[len(b'https://'):] \
                 if is_https \
                 else raw[len(b'http://'):]
-            parts = rest.split(SLASH)
+            parts = rest.split(SLASH, 1)
             host, port = Url.parse_host_and_port(parts[0])
             return cls(
                 scheme=b'https' if is_https else b'http',
                 hostname=host,
                 port=port,
                 remainder=None if len(parts) == 1 else (
-                    SLASH + SLASH.join(parts[1:])
+                    SLASH + parts[1]
                 ),
             )
         host, port = Url.parse_host_and_port(raw)
@@ -105,6 +105,8 @@ class Url:
                 host, port = raw, None
         # patch up invalid ipv6 scenario
         rhost = host.decode('utf-8')
-        if COLON.decode('utf-8') in rhost and rhost[0] != '[' and rhost[-1] != ']':
+        if COLON.decode('utf-8') in rhost and \
+                rhost[0] != '[' and \
+                rhost[-1] != ']':
             host = b'[' + host + b']'
         return host, port
diff --git a/proxy/plugin/modify_chunk_response.py b/proxy/plugin/modify_chunk_response.py
index ccfba8c..f1ace20 100644
--- a/proxy/plugin/modify_chunk_response.py
+++ b/proxy/plugin/modify_chunk_response.py
@@ -37,7 +37,7 @@ class ModifyChunkResponsePlugin(HttpProxyBasePlugin):
         if self.response.state == httpParserStates.COMPLETE:
             # Avoid setting a body for responses where a body is not expected.
             # Otherwise, example curl will report warnings.
-            if self.response.body_expected():
+            if self.response.body_expected:
                 self.response.body = b'\n'.join(self.DEFAULT_CHUNKS) + b'\n'
             self.client.queue(memoryview(self.response.build_response()))
         return memoryview(b'')
diff --git a/proxy/plugin/modify_post_data.py b/proxy/plugin/modify_post_data.py
index 97d6962..21be9d3 100644
--- a/proxy/plugin/modify_post_data.py
+++ b/proxy/plugin/modify_post_data.py
@@ -34,7 +34,7 @@ class ModifyPostDataPlugin(HttpProxyBasePlugin):
             request.body = ModifyPostDataPlugin.MODIFIED_BODY
             # Update Content-Length header only when request is NOT chunked
             # encoded
-            if not request.is_chunked_encoded():
+            if not request.is_chunked_encoded:
                 request.add_header(
                     b'Content-Length',
                     bytes_(len(request.body)),
diff --git a/proxy/plugin/proxy_pool.py b/proxy/plugin/proxy_pool.py
index 7ae7372..1005aca 100644
--- a/proxy/plugin/proxy_pool.py
+++ b/proxy/plugin/proxy_pool.py
@@ -87,7 +87,7 @@ class ProxyPoolPlugin(TcpUpstreamConnectionHandler, HttpProxyBasePlugin):
         # in progress for SSL cache handling.
         #
         # Implement your own logic here e.g. round-robin, least connection etc.
-        endpoint = random.choice(self.flags.proxy_pool)[0].split(':')
+        endpoint = random.choice(self.flags.proxy_pool)[0].split(':', 1)
         if endpoint[0] == 'localhost' and endpoint[1] == '8899':
             return request
         logger.debug('Using endpoint: {0}:{1}'.format(*endpoint))
@@ -142,7 +142,7 @@ class ProxyPoolPlugin(TcpUpstreamConnectionHandler, HttpProxyBasePlugin):
             assert url.hostname
             host, port = url.hostname.decode('utf-8'), url.port
             port = port if port else (
-                443 if request.is_https_tunnel() else 80
+                443 if request.is_https_tunnel else 80
             )
         path = None if not request.path else request.path.decode()
         self.request_host_port_path_method = [
diff --git a/proxy/plugin/redirect_to_custom_server.py b/proxy/plugin/redirect_to_custom_server.py
index d04d7ca..82f4b0b 100644
--- a/proxy/plugin/redirect_to_custom_server.py
+++ b/proxy/plugin/redirect_to_custom_server.py
@@ -24,7 +24,7 @@ class RedirectToCustomServerPlugin(HttpProxyBasePlugin):
             self, request: HttpParser,
     ) -> Optional[HttpParser]:
         # Redirect all non-https requests to inbuilt WebServer.
-        if not request.is_https_tunnel():
+        if not request.is_https_tunnel:
             request.set_url(self.UPSTREAM_SERVER)
             # Update Host header too, otherwise upstream can reject our request
             if request.has_header(b'Host'):
diff --git a/proxy/plugin/web_server_route.py b/proxy/plugin/web_server_route.py
index 52f85de..bfbe4bd 100644
--- a/proxy/plugin/web_server_route.py
+++ b/proxy/plugin/web_server_route.py
@@ -18,6 +18,18 @@ from ..http.parser import HttpParser
 from ..http.websocket import WebsocketFrame
 from ..http.server import HttpWebServerBasePlugin, httpProtocolTypes
 
+HTTP_RESPONSE = memoryview(
+    build_http_response(
+        httpStatusCodes.OK, body=b'HTTP route response',
+    ),
+)
+
+HTTPS_RESPONSE = memoryview(
+    build_http_response(
+        httpStatusCodes.OK, body=b'HTTPS route response',
+    ),
+)
+
 logger = logging.getLogger(__name__)
 
 
@@ -33,21 +45,9 @@ class WebServerPlugin(HttpWebServerBasePlugin):
 
     def handle_request(self, request: HttpParser) -> None:
         if request.path == b'/http-route-example':
-            self.client.queue(
-                memoryview(
-                    build_http_response(
-                        httpStatusCodes.OK, body=b'HTTP route response',
-                    ),
-                ),
-            )
+            self.client.queue(HTTP_RESPONSE)
         elif request.path == b'/https-route-example':
-            self.client.queue(
-                memoryview(
-                    build_http_response(
-                        httpStatusCodes.OK, body=b'HTTPS route response',
-                    ),
-                ),
-            )
+            self.client.queue(HTTPS_RESPONSE)
 
     def on_websocket_open(self) -> None:
         logger.info('Websocket open')


Test patch:
diff --git a/tests/http/test_http_parser.py b/tests/http/test_http_parser.py
index 8d97a8f..2d1cc88 100644
--- a/tests/http/test_http_parser.py
+++ b/tests/http/test_http_parser.py
@@ -56,21 +56,25 @@ class TestHttpParser(unittest.TestCase):
 
     def test_urlparse(self) -> None:
         self.parser.parse(b'CONNECT httpbin.org:443 HTTP/1.1\r\n')
-        self.assertTrue(self.parser.is_https_tunnel())
+        self.assertTrue(self.parser.is_https_tunnel)
+        self.assertFalse(self.parser.is_connection_upgrade)
+        self.assertTrue(self.parser.is_http_1_1_keep_alive)
+        self.assertFalse(self.parser.content_expected)
+        self.assertFalse(self.parser.body_expected)
         self.assertEqual(self.parser.host, b'httpbin.org')
         self.assertEqual(self.parser.port, 443)
         self.assertNotEqual(self.parser.state, httpParserStates.COMPLETE)
 
     def test_urlparse_on_invalid_connect_request(self) -> None:
         self.parser.parse(b'CONNECT / HTTP/1.0\r\n\r\n')
-        self.assertTrue(self.parser.is_https_tunnel())
+        self.assertTrue(self.parser.is_https_tunnel)
         self.assertEqual(self.parser.host, None)
         self.assertEqual(self.parser.port, 443)
         self.assertEqual(self.parser.state, httpParserStates.COMPLETE)
 
     def test_unicode_character_domain_connect(self) -> None:
         self.parser.parse(bytes_('CONNECT ççç.org:443 HTTP/1.1\r\n'))
-        self.assertTrue(self.parser.is_https_tunnel())
+        self.assertTrue(self.parser.is_https_tunnel)
         self.assertEqual(self.parser.host, bytes_('ççç.org'))
         self.assertEqual(self.parser.port, 443)
 
@@ -78,7 +82,7 @@ class TestHttpParser(unittest.TestCase):
         self.parser.parse(
             bytes_('CONNECT 2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF:443 HTTP/1.1\r\n'),
         )
-        self.assertTrue(self.parser.is_https_tunnel())
+        self.assertTrue(self.parser.is_https_tunnel)
         self.assertEqual(
             self.parser.host, bytes_(
                 '[2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF]',
@@ -92,7 +96,7 @@ class TestHttpParser(unittest.TestCase):
                 'CONNECT [2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF]:443 HTTP/1.1\r\n',
             ),
         )
-        self.assertTrue(self.parser.is_https_tunnel())
+        self.assertTrue(self.parser.is_https_tunnel)
         self.assertEqual(
             self.parser.host, bytes_(
                 '[2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF]',
@@ -663,7 +667,7 @@ class TestHttpParser(unittest.TestCase):
                 httpMethods.GET, b'/',
             ),
         )
-        self.assertTrue(self.parser.is_http_1_1_keep_alive())
+        self.assertTrue(self.parser.is_http_1_1_keep_alive)
 
     def test_is_http_1_1_keep_alive_with_non_close_connection_header(
             self,
@@ -676,7 +680,7 @@ class TestHttpParser(unittest.TestCase):
                 },
             ),
         )
-        self.assertTrue(self.parser.is_http_1_1_keep_alive())
+        self.assertTrue(self.parser.is_http_1_1_keep_alive)
 
     def test_is_not_http_1_1_keep_alive_with_close_header(self) -> None:
         self.parser.parse(
@@ -687,7 +691,7 @@ class TestHttpParser(unittest.TestCase):
                 },
             ),
         )
-        self.assertFalse(self.parser.is_http_1_1_keep_alive())
+        self.assertFalse(self.parser.is_http_1_1_keep_alive)
 
     def test_is_not_http_1_1_keep_alive_for_http_1_0(self) -> None:
         self.parser.parse(
@@ -695,7 +699,7 @@ class TestHttpParser(unittest.TestCase):
                 httpMethods.GET, b'/', protocol_version=b'HTTP/1.0',
             ),
         )
-        self.assertFalse(self.parser.is_http_1_1_keep_alive())
+        self.assertFalse(self.parser.is_http_1_1_keep_alive)
 
     def test_paramiko_doc(self) -> None:
         response = b'HTTP/1.1 304 Not Modified\r\nDate: Tue, 03 Dec 2019 02:31:55 GMT\r\nConnection: keep-alive' \