Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=en>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Async Python Frameworks — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <!-- Canonical URL for SEO purposes -->
  28. <link rel="canonical" href="https://larlet.fr/david/blog/2017/async-python-frameworks/">
  29. <!-- SEO/Semantic metadata -->
  30. <meta name="description" content="Have fun roll-ing out your own framework!" />
  31. <meta name="twitter:description" property="og:description" itemprop="description" content="Have fun roll-ing out your own framework!" />
  32. <meta name="twitter:title" property="og:title" itemprop="name" content="Async Python Frameworks" />
  33. <meta name="twitter:card" content="summary" />
  34. <meta name="twitter:creator" content="@davidbgk" />
  35. <meta name="twitter:url" property="og:url" content="https://larlet.fr/david/blog/2017/async-python-frameworks/" />
  36. <meta property="og:type" content="article" />
  37. <meta property="og:site_name" content="David Larlet (@davidbgk)" />
  38. <meta name="twitter:image" property="og:image" itemprop="image" content="https://larlet.fr/static/david/blog/2017/async-python-frameworks.jpg" />
  39. <style>
  40. /* http://meyerweb.com/eric/tools/css/reset/ */
  41. html, body, div, span,
  42. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  43. a, abbr, address, big, cite, code,
  44. del, dfn, em, img, ins,
  45. small, strike, strong, tt, var,
  46. dl, dt, dd, ol, ul, li,
  47. fieldset, form, label, legend,
  48. table, caption, tbody, tfoot, thead, tr, th, td,
  49. article, aside, canvas, details, embed,
  50. figure, figcaption, footer, header, hgroup,
  51. menu, nav, output, ruby, section, summary,
  52. time, mark, audio, video {
  53. margin: 0;
  54. padding: 0;
  55. border: 0;
  56. font-size: 100%;
  57. font: inherit;
  58. vertical-align: baseline;
  59. }
  60. /* HTML5 display-role reset for older browsers */
  61. article, aside, details, figcaption, figure,
  62. footer, header, hgroup, menu, nav, section { display: block; }
  63. body { line-height: 1; }
  64. blockquote, q { quotes: none; }
  65. blockquote:before, blockquote:after,
  66. q:before, q:after {
  67. content: '';
  68. content: none;
  69. }
  70. table {
  71. border-collapse: collapse;
  72. border-spacing: 0;
  73. }
  74. /* http://practicaltypography.com/equity.html */
  75. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  76. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: normal;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  89. font-weight: 300;
  90. font-style: italic;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'EquityTextB';
  95. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  96. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  97. font-weight: 700;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. @font-face {
  102. font-family: 'ConcourseT3';
  103. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  104. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  105. font-weight: 300;
  106. font-style: normal;
  107. font-display: swap;
  108. }
  109. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  110. body {
  111. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  112. font-family: "EquityTextB", Palatino, serif;
  113. background-color: #f0f0ea;
  114. color: #07486c;
  115. font-kerning: normal;
  116. -moz-osx-font-smoothing: grayscale;
  117. -webkit-font-smoothing: subpixel-antialiased;
  118. text-rendering: optimizeLegibility;
  119. font-variant-ligatures: common-ligatures contextual;
  120. font-feature-settings: "kern", "liga", "clig", "calt";
  121. }
  122. pre, code, kbd, samp, var, tt {
  123. font-family: 'TriplicateT4c', monospace;
  124. }
  125. em {
  126. font-style: italic;
  127. color: #323a45;
  128. }
  129. strong {
  130. font-weight: bold;
  131. color: black;
  132. }
  133. nav {
  134. background-color: #323a45;
  135. color: #f0f0ea;
  136. display: flex;
  137. justify-content: space-around;
  138. padding: 1rem .5rem;
  139. }
  140. nav:last-child {
  141. border-bottom: 1vh solid #2d7474;
  142. }
  143. nav a {
  144. color: #f0f0ea;
  145. }
  146. nav abbr {
  147. border-bottom: 1px dotted white;
  148. }
  149. h1 {
  150. border-top: 1vh solid #2d7474;
  151. border-bottom: .2vh dotted #2d7474;
  152. background-color: #e3e1e1;
  153. color: #323a45;
  154. text-align: center;
  155. padding: 5rem 0 4rem 0;
  156. width: 100%;
  157. font-family: 'ConcourseT3';
  158. display: flex;
  159. flex-direction: column;
  160. }
  161. h1.single {
  162. padding-bottom: 10rem;
  163. }
  164. h1 span {
  165. position: absolute;
  166. top: 1vh;
  167. left: 20%;
  168. line-height: 0;
  169. }
  170. h1 span a {
  171. line-height: 1.7;
  172. padding: 1rem 1.2rem .6rem 1.2rem;
  173. border-radius: 0 0 6% 6%;
  174. background: #2d7474;
  175. font-size: 1.3rem;
  176. color: white;
  177. text-decoration: none;
  178. }
  179. h2 {
  180. margin: 4rem 0 1rem;
  181. border-top: .2vh solid #2d7474;
  182. padding-top: 1vh;
  183. }
  184. h3 {
  185. text-align: center;
  186. margin: 3rem 0 .75em;
  187. }
  188. hr {
  189. height: .4rem;
  190. width: .4rem;
  191. border-radius: .4rem;
  192. background: #07486c;
  193. margin: 2.5rem auto;
  194. }
  195. time {
  196. display: bloc;
  197. margin-left: 0 !important;
  198. }
  199. ul, ol {
  200. margin: 2rem;
  201. }
  202. ul {
  203. list-style-type: square;
  204. }
  205. a {
  206. text-decoration-skip-ink: auto;
  207. text-decoration-thickness: 0.05em;
  208. text-underline-offset: 0.09em;
  209. }
  210. article {
  211. max-width: 50rem;
  212. display: flex;
  213. flex-direction: column;
  214. margin: 2rem auto;
  215. }
  216. article.single {
  217. border-top: .2vh dotted #2d7474;
  218. margin: -6rem auto 1rem auto;
  219. background: #f0f0ea;
  220. padding: 2rem;
  221. }
  222. article p:last-child {
  223. margin-bottom: 1rem;
  224. }
  225. p {
  226. padding: 0 .5rem;
  227. margin-left: 3rem;
  228. }
  229. p + p,
  230. figure + p {
  231. margin-top: 2rem;
  232. }
  233. blockquote {
  234. background-color: #e3e1e1;
  235. border-left: .5vw solid #2d7474;
  236. display: flex;
  237. flex-direction: column;
  238. align-items: center;
  239. padding: 1rem;
  240. margin: 1.5rem;
  241. }
  242. blockquote cite {
  243. font-style: italic;
  244. }
  245. blockquote p {
  246. margin-left: 0;
  247. }
  248. figure {
  249. border-top: .2vh solid #2d7474;
  250. background-color: #e3e1e1;
  251. text-align: center;
  252. padding: 1.5rem 0;
  253. margin: 1rem 0 0;
  254. font-size: 1.5rem;
  255. width: 100%;
  256. }
  257. figure img {
  258. max-width: 250px;
  259. max-height: 250px;
  260. border: .5vw solid #323a45;
  261. padding: 1px;
  262. }
  263. figcaption {
  264. padding: 1rem;
  265. line-height: 1.4;
  266. }
  267. aside {
  268. display: flex;
  269. flex-direction: column;
  270. background-color: #e3e1e1;
  271. padding: 1rem 0;
  272. border-bottom: .2vh solid #07486c;
  273. }
  274. aside p {
  275. max-width: 50rem;
  276. margin: 0 auto;
  277. }
  278. /* https://fvsch.com/code/css-locks/ */
  279. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  280. font-size: 1rem;
  281. line-height: calc( 1.5em + 0.2 * 1rem );
  282. }
  283. h1 {
  284. font-size: 1.9rem;
  285. line-height: calc( 1.2em + 0.2 * 1rem );
  286. }
  287. h2 {
  288. font-size: 1.6rem;
  289. line-height: calc( 1.3em + 0.2 * 1rem );
  290. }
  291. h3 {
  292. font-size: 1.35rem;
  293. line-height: calc( 1.4em + 0.2 * 1rem );
  294. }
  295. @media (min-width: 20em) {
  296. /* The (100vw - 20rem) / (50 - 20) part
  297. resolves to 0-1rem, depending on the
  298. viewport width (between 20em and 50em). */
  299. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  300. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  301. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  302. margin-left: 0;
  303. }
  304. h1 {
  305. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. h2 {
  309. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  310. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  311. }
  312. h3 {
  313. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  314. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  315. }
  316. }
  317. @media (min-width: 50em) {
  318. /* The right part of the addition *must* be a
  319. rem value. In this example we *could* change
  320. the whole declaration to font-size:2.5rem,
  321. but if our baseline value was not expressed
  322. in rem we would have to use calc. */
  323. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  324. font-size: calc( 1rem + .6 * 1rem );
  325. line-height: 1.5em;
  326. }
  327. p, li, pre, details {
  328. margin-left: 3rem;
  329. }
  330. h1 {
  331. font-size: calc( 1.9rem + 1.5 * 1rem );
  332. line-height: 1.2em;
  333. }
  334. h2 {
  335. font-size: calc( 1.5rem + 1.5 * 1rem );
  336. line-height: 1.3em;
  337. }
  338. h3 {
  339. font-size: calc( 1.35rem + 1.5 * 1rem );
  340. line-height: 1.4em;
  341. }
  342. figure img {
  343. max-width: 500px;
  344. max-height: 500px;
  345. }
  346. }
  347. figure.unsquared {
  348. margin-bottom: 1.5rem;
  349. }
  350. figure.unsquared img {
  351. height: inherit;
  352. }
  353. @media print {
  354. body { font-size: 100%; }
  355. a:after { content: " (" attr(href) ")"; }
  356. a, a:link, a:visited, a:after {
  357. text-decoration: underline;
  358. text-shadow: none !important;
  359. background-image: none !important;
  360. background: white;
  361. color: black;
  362. }
  363. abbr[title] { border-bottom: 0; }
  364. abbr[title]:after { content: " (" attr(title) ")"; }
  365. img { page-break-inside: avoid; }
  366. @page { margin: 2cm .5cm; }
  367. h1, h2, h3 { page-break-after: avoid; }
  368. p3 { orphans: 3; widows: 3; }
  369. img {
  370. max-width: 250px !important;
  371. max-height: 250px !important;
  372. }
  373. nav, aside { display: none; }
  374. }
  375. ul.with_columns {
  376. column-count: 1;
  377. }
  378. @media (min-width: 20em) {
  379. ul.with_columns {
  380. column-count: 2;
  381. }
  382. }
  383. @media (min-width: 50em) {
  384. ul.with_columns {
  385. column-count: 3;
  386. }
  387. }
  388. ul.with_two_columns {
  389. column-count: 1;
  390. }
  391. @media (min-width: 20em) {
  392. ul.with_two_columns {
  393. column-count: 1;
  394. }
  395. }
  396. @media (min-width: 50em) {
  397. ul.with_two_columns {
  398. column-count: 2;
  399. }
  400. }
  401. .gallery {
  402. display: flex;
  403. flex-wrap: wrap;
  404. justify-content: space-around;
  405. }
  406. .gallery figure img {
  407. margin-left: 1rem;
  408. margin-right: 1rem;
  409. }
  410. .gallery figure figcaption {
  411. font-family: 'ConcourseT3'
  412. }
  413. footer {
  414. font-family: 'ConcourseT3';
  415. display: flex;
  416. flex-direction: column;
  417. border-top: 3px solid white;
  418. padding: 4rem 0;
  419. background-color: #07486c;
  420. color: white;
  421. }
  422. footer > * {
  423. max-width: 50rem;
  424. margin: 0 auto;
  425. }
  426. footer a {
  427. color: #f1c40f;
  428. }
  429. footer .avatar {
  430. width: 200px;
  431. height: 200px;
  432. border-radius: 50%;
  433. float: left;
  434. -webkit-shape-outside: circle();
  435. shape-outside: circle();
  436. margin-right: 2rem;
  437. padding: 2px 5px 5px 2px;
  438. background: white;
  439. border-left: 1px solid #f1c40f;
  440. border-top: 1px solid #f1c40f;
  441. border-right: 5px solid #f1c40f;
  442. border-bottom: 5px solid #f1c40f;
  443. }
  444. </style>
  445. <h1 class="single">
  446. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  447. Async Python Frameworks
  448. <time>Publié le 18 novembre 2017</time>
  449. </h1>
  450. <article class="single">
  451. <blockquote>
  452. <p>There is a new lightweight, fast, minimalist, you-name-it framework each week within the Python community. Why? Because it’s simple! You may want to take that path too and if you do, don’t make the same mistakes as we did. Come share your own frustrations and let’s build a new one together, in 10 minutes 🙃 — <a href="https://2017.pycon.ca/schedule/21/">How to build an async pico web framework from scratch</a></p>
  453. </blockquote>
  454. <p>I gave a <a href="/david/talks/">talk</a> at PyConCA on that topic, feeling legitimate given that <a href="http://roll.readthedocs.io/">we actually did it</a>! Here are some notes about my intervention.</p>
  455. <h2>Context</h2>
  456. <p>I’m working with the French government via <a href="https://beta.gouv.fr/en/about/">State Startups</a>. Our goal is to explore solutions for a given problem faced by citizens and/or administrations. These last 6 months, we <a href="https://beta.gouv.fr/startup/api-drones.html">experimented a way to localize unmanned aerial vehicles</a> (UAVs a.k.a. drones) in real-time for <a href="https://drone.beta.gouv.fr/">the whole French territory</a>. With a <a href="/david/blog/2017/distributed-teams/">distributed team</a>, we tried to figure out if the law was applicable and if a centralized database would make sense for all parties (citizens, industrials, police, army and so on).</p>
  457. <p>There was a bunch of unknowns, especially related to performances. When you are (in)validating proof of concepts, you might think that performances are your last concern but we wanted to be able to try the service for real. Plus, you never know when a proof-of-concept finally goes into production…</p>
  458. <p>We roughly estimated that we will have to be able to handle about 50k requests per second. In Python. With a unique server. Spoiler alert: for a lot of non-technical reasons, we did not stressed the API for real so we cannot guarantee that our framework can hit that peak. Nonetheless, I think the process was interesting because that is the first time in my career that I had to put my developments under such constraints.</p>
  459. <p>We knew <a href="https://www.djangoproject.com/">Django</a> and <a href="http://flask.pocoo.org/">Flask</a>, we already experimented with <a href="http://falcon.readthedocs.io/">Falcon</a>. You cannot achieve that level of performances out of these frameworks given our resources. That is why we dug into the async side of Python 3. And it was fun! At first we thought that <a href="http://sanic.readthedocs.io/en/latest/">Sanic</a> was the perfect candidate but soon enough we bumped into issues related to testing and the API did not click with us. So, after an intensive workweek of sleep deprivation and big waves in the ocean, <a href="http://roll.readthedocs.io/en/latest/discussions/#why-roll">we started to hack on our own framework</a>. And it was crazy!</p>
  460. <h2>Coopetition</h2>
  461. <p>First of all, this is not a competition. It is challenging to compare concepts and implementations. We had no guilt to steal clever parts, documenting what we think is better for our needs. What we made is not a framework for anybody. Micro means specific and as such incomplete. That is why I encourage you to do the same <em>for your own needs</em>.</p>
  462. <p>Go read the code of your current web framework, it is a gold mine of hacks to deal with inconsistent HTTP-related specifications :-). Seriously, assembling your own framework will make you learn a ton of things. From request parsing to header security, from CORS-related issues to multipart nightmare of <code>latin-1</code> encoded filenames, from conflicting specifications on Cookies to crazy <code>Accept</code> headers.</p>
  463. <p>Our philosophy was to give a decent developer experience while uncompromising performances. To achieve that we knew that we had to stay minimalists and reuse small and performant librairies.</p>
  464. <h2>Benchmarks</h2>
  465. <p>We are still working on it and for sure a benchmark is a lie. We use <a href="https://github.com/wg/wrk">wrk</a> which is kind of <a href="https://httpd.apache.org/docs/2.4/programs/ab.html">ab</a> on steroids. Anyway, I think we are missing a tool in the Python community to be able to compare performances across pull/merge-requests. Like continuous integration with metrics on introduced bottlenecks, continuous performances? If I missed anything on that topic, please drop me an email.</p>
  466. <p><em>Edit: <a href="http://www.pocketsensei.fr/">Ronan</a> made me discover <a href="https://asv.readthedocs.io/">airspeed velocity</a>, nice!</em></p>
  467. <p>The smallest async web response we were able to compute is the following:</p>
  468. <div class="codehilite"><pre><span class="k">class</span> <span class="nc">Protocol</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">Protocol</span><span class="p">):</span>
  469. <span class="k">def</span> <span class="nf">connection_made</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">transport</span><span class="p">):</span>
  470. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span> <span class="o">=</span> <span class="n">transport</span>
  471. <span class="k">def</span> <span class="nf">data_received</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">):</span>
  472. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">b</span><span class="s">&#39;HTTP/1.1 200 OK</span><span class="se">\r\n</span><span class="s">&#39;</span><span class="p">)</span>
  473. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">b</span><span class="s">&#39;Content-Length: 25</span><span class="se">\r\n</span><span class="s">&#39;</span><span class="p">)</span>
  474. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">b</span><span class="s">&#39;Content-Type: application/json</span><span class="se">\r\n</span><span class="s">&#39;</span><span class="p">)</span>
  475. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">b</span><span class="s">&#39;</span><span class="se">\r\n</span><span class="s">&#39;</span><span class="p">)</span>
  476. <span class="bp">self</span><span class="o">.</span><span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">b</span><span class="s">&#39;{&quot;message&quot;:&quot;Hello bench&quot;}&#39;</span><span class="p">)</span>
  477. </pre></div>
  478. <p>Once you have that hardcoded response, set <code>uvloop</code> as your event loop and create a task for it:</p>
  479. <div class="codehilite"><pre><span class="n">asyncio</span><span class="o">.</span><span class="n">set_event_loop_policy</span><span class="p">(</span><span class="n">uvloop</span><span class="o">.</span><span class="n">EventLoopPolicy</span><span class="p">())</span>
  480. <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
  481. <span class="n">server</span> <span class="o">=</span> <span class="n">loop</span><span class="o">.</span><span class="n">create_server</span><span class="p">(</span><span class="n">Protocol</span><span class="p">,</span> <span class="s">&#39;127.0.0.1&#39;</span><span class="p">,</span> <span class="mi">8000</span><span class="p">)</span>
  482. <span class="n">loop</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">server</span><span class="p">)</span>
  483. </pre></div>
  484. <p>Here we go, for more details, check out <a href="https://github.com/pyrates/roll/blob/master/benchmarks/native/app.py">the whole file in our benchmarks</a>. But basically you have all important parts here. That’s great but a bit too low-level for our taste! </p>
  485. <p>Enters <a href="http://roll.readthedocs.io/">Roll</a> which adds routing and request/response parsing in <a href="https://github.com/pyrates/roll/blob/master/roll/__init__.py">roughly 300 lines of code</a>. An example of the same kind of response would be:</p>
  486. <div class="codehilite"><pre><span class="n">asyncio</span><span class="o">.</span><span class="n">set_event_loop_policy</span><span class="p">(</span><span class="n">uvloop</span><span class="o">.</span><span class="n">EventLoopPolicy</span><span class="p">())</span>
  487. <span class="n">app</span> <span class="o">=</span> <span class="n">Roll</span><span class="p">()</span>
  488. <span class="nd">@app.route</span><span class="p">(</span><span class="s">&#39;/hello/{parameter}&#39;</span><span class="p">)</span>
  489. <span class="n">async</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">parameter</span><span class="p">):</span>
  490. <span class="n">response</span><span class="o">.</span><span class="n">json</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#39;message&#39;</span><span class="p">:</span> <span class="n">f</span><span class="s">&#39;Hello {parameter}&#39;</span><span class="p">}</span>
  491. </pre></div>
  492. <p>We have routing and JSON serialization under the hood with dynamic response given the passed <code>parameter</code> in URL. No big deal but it’s quite handy compared to the raw version. Note that we lost about 63% of the initial number of requests per seconds <em>just</em> doing that!</p>
  493. <p><em>Hint: it’s still better than the “concurrence”.</em> 😇</p>
  494. <h2>Optimizations</h2>
  495. <blockquote>
  496. <p>Python 4 is already here, and it’s called Cython. — My coworker</p>
  497. </blockquote>
  498. <p><em>Me: <a href="https://developers.redhat.com/blog/2017/11/16/speed-python-using-rust/">Rust is probably Python 5</a> (<a href="/david/cache/6a759885012094d09d4b2d9f8f96fbbc/">cache</a>)! It escalates quickly when we are trolling :p</em></p>
  499. <p><strike>We</strike> My <a href="http://yohanboniface.me/">crazy coworker</a> wrote a <a href="https://github.com/pyrates/autoroutes">routing system</a> using Cython to improve performances and the jump was significative to say the least. That is part of our approach: finding small existing pieces or write our own if we think we can code a faster solution. Once you expose what you consider is the right API, it’s easy to swap a given piece from one to another without introducing breaking changes.</p>
  500. <p>For instance, we are currently using the awesome <a href="https://github.com/MagicStack/httptools">httptools</a> to parse the request and the URL but that might change if we find a better approach. We only have these two dependencies for now and that is by design. We are really careful and conservative on introducing new features, trying to keep aesthetic in mind. Each new addition is benchmarked and heavily discussed in terms of developer experience. This is always a tradeoff.</p>
  501. <p>One of our core principles is to ease the plug-ability via extensions and custom subclasses of core components. We are missing a way to get benchmarks of these additions though. Maybe tools like <a href="https://github.com/haypo/perf">perf</a> will help in the future, ideally it would allow us to make a table of performances given the activated extensions. Too many benchmarks are way far from real-life requests/responses treatments.</p>
  502. <p>An option we did not explore yet is the use of newly introduced annotations like did <a href="https://github.com/encode/apistar#performance">APIStar</a> to only parse/evaluate pertinent parts of the incoming request. Not sure it will work in our particular case though.</p>
  503. <h2>More than code</h2>
  504. <p>That part is really intimate. I enjoy taking the time to carve my own tools and challenge my assumptions with experimented colleagues. I can feel the progression, both on a personal point of view and as a team. You might think that time is lost and building your own “homemade framework” is such a waste and will be painful to maintain. That is OK and I used to think that too.</p>
  505. <p>With as much hindsight as I can under such short notice, I think it saved us as a team to build that core reusable component when we were targeted by politics-related issues and pressure from all parts. It was kind of a safe place where we can focus for half a day and come into that <em>pair-flow</em> state, knowing that we were producing an open-source common.</p>
  506. <h2>Take aways</h2>
  507. <p><strong>Do it!</strong> It has been a hell of a rollercoaster to code our own solution but we are quite happy of the result and we know that we will reuse it for future and ancient projects so the return over investment is not null even if we did not put the actual project to production. And by the way, the technical challenges along the way were as much interesting as the end result so no regrets whatsoever. Bonus: it built confidence in our capability to overcome problems and made us a better team.</p>
  508. <p><strong>Don’t do it!!!</strong> If you are not mature enough as a team to take the time to craft a tool that will fit your needs, it is probably better to reuse existing ones. It will be more generic and there is no problem with that: 99% of the time this is the more pertinent approach!</p>
  509. <p><em>Have fun roll-ing out your own framework!</em></p>
  510. </article>
  511. <figure class="image" property="schema:image">
  512. <img src="/static/david/blog/2017/async-python-frameworks.jpg" alt="" />
  513. </figure>
  514. <nav id="jumpto">
  515. <p>
  516. <a rel=prev href="/david/blog/2017/distributed-teams/">← Distributed teams</a> | <a href="/david/blog/" title="Retour à la liste des expériences">↑</a> | <a rel=next href="/david/blog/2017/bugs-complexite/">Bugs et complexité →</a>
  517. </p>
  518. </nav>
  519. <footer>
  520. <div>
  521. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  522. <p>
  523. Bonjour/Hi!
  524. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  525. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  526. </p>
  527. <p>
  528. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  529. </p>
  530. <p>
  531. Les dernières publications hebdomadaires sont :
  532. </p>
  533. <ul class="with_columns">
  534. <li>
  535. <a href="/david/stream/2019/12/31/">Merci</a>
  536. </li>
  537. <li>
  538. <a href="/david/stream/2019/12/27/">Intemporels</a>
  539. </li>
  540. <li>
  541. <a href="/david/stream/2019/12/24/">Outils</a>
  542. </li>
  543. <li>
  544. <a href="/david/stream/2019/12/17/">Origines</a>
  545. </li>
  546. <li>
  547. <a href="/david/stream/2019/12/10/">Publier</a>
  548. </li>
  549. <li>
  550. <a href="/david/stream/2019/12/03/">En forêt</a>
  551. </li>
  552. <li>
  553. <a href="/david/stream/2019/11/26/">Ecocentric</a>
  554. </li>
  555. <li>
  556. <a href="/david/stream/2019/11/19/">Se livrer</a>
  557. </li>
  558. <li>
  559. <a href="/david/stream/2019/11/12/">Dépendances</a>
  560. </li>
  561. <li>
  562. <a href="/david/stream/2019/11/05/">Positif</a>
  563. </li>
  564. <li>
  565. <a href="/david/stream/2019/10/29/">Dettes</a>
  566. </li>
  567. <li>
  568. <a href="/david/stream/2019/10/22/">Privilèges</a>
  569. </li>
  570. <li>
  571. <a href="/david/stream/2019/10/15/">Discrétion</a>
  572. </li>
  573. <li>
  574. <a href="/david/stream/2019/10/08/">Désespérance</a>
  575. </li>
  576. <li>
  577. <a href="/david/stream/2019/10/01/">Présent</a>
  578. </li>
  579. <li>
  580. <a href="/david/stream/2019/09/24/">Manifester</a>
  581. </li>
  582. <li>
  583. <a href="/david/stream/2019/09/17/">Arpenter</a>
  584. </li>
  585. <li>
  586. <a href="/david/stream/2019/09/10/">Nostalgie</a>
  587. </li>
  588. <li>
  589. <a href="/david/stream/2019/09/03/">Déconstruire</a>
  590. </li>
  591. <li>
  592. <a href="/david/stream/2019/08/27/">Documenter</a>
  593. </li>
  594. <li>
  595. <a href="/david/stream/2019/08/20/">Frustration</a>
  596. </li>
  597. <li>
  598. <a href="/david/stream/2019/08/13/">Holisme</a>
  599. </li>
  600. <li>
  601. <a href="/david/stream/2019/08/06/">1%</a>
  602. </li>
  603. <li>
  604. <a href="/david/stream/2019/07/30/">Exemplarité</a>
  605. </li>
  606. <li>
  607. <a href="/david/stream/2019/07/23/">Timelines</a>
  608. </li>
  609. <li>
  610. <a href="/david/stream/2019/07/16/">Écoute</a>
  611. </li>
  612. <li>
  613. <a href="/david/stream/2019/07/02/">Anxiété</a>
  614. </li>
  615. <li>
  616. <a href="/david/stream/2019/06/21/">À lier</a>
  617. </li>
  618. <li>
  619. <a href="/david/stream/2019/06/14/">Pauvreté</a>
  620. </li>
  621. <li>
  622. <a href="/david/stream/2019/06/07/">Amateur</a>
  623. </li>
  624. <li>
  625. <a href="/david/stream/2019/05/31/">Pollution</a>
  626. </li>
  627. <li>
  628. <a href="/david/stream/2019/05/24/">Apaisement</a>
  629. </li>
  630. <li>
  631. <a href="/david/stream/2019/05/10/">Folie</a>
  632. </li>
  633. <li>
  634. <a href="/david/stream/2019/05/03/">Sympathie</a>
  635. </li>
  636. <li>
  637. <a href="/david/stream/2019/04/12/">Péremption</a>
  638. </li>
  639. <li>
  640. <a href="/david/stream/2019/04/05/">Définitions</a>
  641. </li>
  642. <li>
  643. <a href="/david/stream/2019/03/29/">Acceptation</a>
  644. </li>
  645. <li>
  646. <a href="/david/stream/2019/03/22/">Dissonance</a>
  647. </li>
  648. <li>
  649. <a href="/david/stream/2019/03/15/">Reconnaissance</a>
  650. </li>
  651. <li>
  652. <a href="/david/stream/2019/03/08/">Lecture</a>
  653. </li>
  654. <li>
  655. <a href="/david/stream/2019/03/01/">Journaux</a>
  656. </li>
  657. <li>
  658. <a href="/david/stream/2019/02/22/">Écriture</a>
  659. </li>
  660. <li>
  661. <a href="/david/stream/2019/02/15/">Kyriarchie</a>
  662. </li>
  663. <li>
  664. <a href="/david/stream/2019/02/08/">Mots-serrures</a>
  665. </li>
  666. <li>
  667. <a href="/david/stream/2019/02/01/">Sans voie</a>
  668. </li>
  669. <li>
  670. <a href="/david/stream/2019/01/25/">Auto-diagnostic</a>
  671. </li>
  672. <li>
  673. <a href="/david/stream/2019/01/18/">Agilité</a>
  674. </li>
  675. <li>
  676. <a href="/david/stream/2019/01/11/">Métaphores</a>
  677. </li>
  678. <li>
  679. <a href="/david/stream/2019/01/04/">Balbutiements</a>
  680. </li>
  681. </ul>
  682. <p>
  683. Voici quelques articles choisis :
  684. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  685. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  686. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  687. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  688. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  689. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  690. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  691. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  692. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  693. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  694. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  695. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  696. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  697. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  698. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  699. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  700. </p>
  701. <p>
  702. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  703. </p>
  704. <p>
  705. Je ne traque pas ta navigation mais mon
  706. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  707. conserve des logs d’accès.
  708. </p>
  709. </div>
  710. </footer>
  711. <script type="text/javascript">
  712. ;(_ => {
  713. const jumper = document.getElementById('jumper')
  714. jumper.addEventListener('click', e => {
  715. e.preventDefault()
  716. const anchor = e.target.getAttribute('href')
  717. const targetEl = document.getElementById(anchor.substring(1))
  718. targetEl.scrollIntoView({behavior: 'smooth'})
  719. })
  720. })()
  721. </script>
  722. <script>
  723. /* Service workers */
  724. if (navigator.serviceWorker) {
  725. window.addEventListener('load', function () {
  726. var selector = 'a[href^="/david/cache/"], a[rel=prev], a[rel=next]'
  727. function sendLinks (selector) {
  728. var links = [].slice.call(document.querySelectorAll(selector)).map(function (link) {
  729. return link.getAttribute('href')
  730. })
  731. links.push(location.pathname) // Put the current page in cache too.
  732. navigator.serviceWorker.controller.postMessage({ links: links })
  733. }
  734. navigator.serviceWorker.getRegistration()
  735. .then(function (registration) {
  736. if (!registration || !navigator.serviceWorker.controller) {
  737. return navigator.serviceWorker.register('/serviceworker.js')
  738. .then(navigator.serviceWorker.ready)
  739. .then(function () {
  740. console.log('[ServiceWorker] Ready to go!')
  741. })
  742. .catch(console.error.bind(console))
  743. } else {
  744. console.log('[ServiceWorker] Send links via registration')
  745. sendLinks(selector)
  746. }
  747. })
  748. navigator.serviceWorker.addEventListener('controllerchange', function () {
  749. console.log('[ServiceWorker] Send links via controller change')
  750. sendLinks(selector)
  751. })
  752. navigator.serviceWorker.addEventListener('message', function (event) {
  753. var link = document.querySelector('a[href="' + event.data.link + '"]')
  754. if (event.data.status && link) {
  755. link.style.backgroundColor = '#2d7474'
  756. link.style.color = '#f0f0ea'
  757. link.setAttribute('title', 'En cache pour consultation sans connexion')
  758. }
  759. })
  760. })
  761. } else {
  762. console.warn('[ServiceWorker] No cache for old browsers.')
  763. }
  764. </script>