From ee8ede0fb8f388fb11e979e0b27022f634d35f62 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:21:09 +0000 Subject: [PATCH] Deployed 2d0945c with MkDocs version: 1.4.2 --- .nojekyll | 0 404.html | 414 + assets/_mkdocstrings.css | 16 + assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.b4d07000.min.js | 29 + assets/javascripts/bundle.b4d07000.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.208ed371.min.js | 42 + .../workers/search.208ed371.min.js.map | 8 + assets/stylesheets/main.26e3688c.min.css | 1 + assets/stylesheets/main.26e3688c.min.css.map | 1 + assets/stylesheets/palette.ecc896b0.min.css | 1 + .../stylesheets/palette.ecc896b0.min.css.map | 1 + async/index.html | 1193 +++ connection-pools/index.html | 930 +++ connections/index.html | 829 ++ exceptions/index.html | 469 ++ extensions/index.html | 928 +++ http2/index.html | 734 ++ index.html | 574 ++ logging/index.html | 482 ++ network-backends/index.html | 878 +++ objects.inv | Bin 0 -> 461 bytes proxies/index.html | 785 ++ quickstart/index.html | 818 ++ requests-responses-urls/index.html | 845 +++ search/search_index.json | 1 + sitemap.xml | 68 + sitemap.xml.gz | Bin 0 -> 315 bytes table-of-contents/index.html | 512 ++ 61 files changed, 17766 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 assets/_mkdocstrings.css create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.b4d07000.min.js create mode 100644 assets/javascripts/bundle.b4d07000.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js.map create mode 100644 assets/stylesheets/main.26e3688c.min.css create mode 100644 assets/stylesheets/main.26e3688c.min.css.map create mode 100644 assets/stylesheets/palette.ecc896b0.min.css create mode 100644 assets/stylesheets/palette.ecc896b0.min.css.map create mode 100644 async/index.html create mode 100644 connection-pools/index.html create mode 100644 connections/index.html create mode 100644 exceptions/index.html create mode 100644 extensions/index.html create mode 100644 http2/index.html create mode 100644 index.html create mode 100644 logging/index.html create mode 100644 network-backends/index.html create mode 100644 objects.inv create mode 100644 proxies/index.html create mode 100644 quickstart/index.html create mode 100644 requests-responses-urls/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 table-of-contents/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..32b10b16 --- /dev/null +++ b/404.html @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css new file mode 100644 index 00000000..b2cceef2 --- /dev/null +++ b/assets/_mkdocstrings.css @@ -0,0 +1,16 @@ + +/* Don't capitalize names. */ +h5.doc-heading { + text-transform: none !important; +} + +/* Avoid breaking parameters name, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* For pieces of Markdown rendered in table cells. */ +.doc-contents td p { + margin-top: 0 !important; + margin-bottom: 0 !important; +} diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.b4d07000.min.js b/assets/javascripts/bundle.b4d07000.min.js new file mode 100644 index 00000000..3c0bdad9 --- /dev/null +++ b/assets/javascripts/bundle.b4d07000.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ci=Object.create;var gr=Object.defineProperty;var Ri=Object.getOwnPropertyDescriptor;var ki=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Hi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,nn=Object.prototype.propertyIsEnumerable;var rn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&rn(e,r,t[r]);if(Ht)for(var r of Ht(t))nn.call(t,r)&&rn(e,r,t[r]);return e};var on=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&nn.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Pi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ki(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=Ri(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ci(Hi(e)):{},Pi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var sn=Pt((xr,an)=>{(function(e,t){typeof xr=="object"&&typeof an!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(O){return!!(O&&O!==document&&O.nodeName!=="HTML"&&O.nodeName!=="BODY"&&"classList"in O&&"contains"in O.classList)}function f(O){var Qe=O.type,De=O.tagName;return!!(De==="INPUT"&&s[Qe]&&!O.readOnly||De==="TEXTAREA"&&!O.readOnly||O.isContentEditable)}function c(O){O.classList.contains("focus-visible")||(O.classList.add("focus-visible"),O.setAttribute("data-focus-visible-added",""))}function u(O){O.hasAttribute("data-focus-visible-added")&&(O.classList.remove("focus-visible"),O.removeAttribute("data-focus-visible-added"))}function p(O){O.metaKey||O.altKey||O.ctrlKey||(a(r.activeElement)&&c(r.activeElement),n=!0)}function m(O){n=!1}function d(O){a(O.target)&&(n||f(O.target))&&c(O.target)}function h(O){a(O.target)&&(O.target.classList.contains("focus-visible")||O.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(O.target))}function v(O){document.visibilityState==="hidden"&&(o&&(n=!0),Y())}function Y(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function B(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(O){O.target.nodeName&&O.target.nodeName.toLowerCase()==="html"||(n=!1,B())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),Y(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var cn=Pt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},s=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(B,N){d.append(N,B)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(O){throw new Error("URL unable to set base "+c+" due to "+O)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,Y=!0,B=this;["append","delete","set"].forEach(function(O){var Qe=h[O];h[O]=function(){Qe.apply(h,arguments),v&&(Y=!1,B.search=h.toString(),Y=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,Y&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(f){Object.defineProperty(s,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){a(f)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var qr=Pt((Mt,Nr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof Nr=="object"?Nr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ai}});var s=i(279),a=i.n(s),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(T){return!1}}var d=function(T){var E=p()(T);return m("cut"),E},h=d;function v(j){var T=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[T?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var Y=function(T,E){var H=v(T);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},B=function(T){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof T=="string"?H=Y(T,E):T instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(T==null?void 0:T.type)?H=Y(T.value,E):(H=p()(T),m("copy")),H},N=B;function O(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?O=function(E){return typeof E}:O=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},O(j)}var Qe=function(){var T=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=T.action,H=E===void 0?"copy":E,I=T.container,q=T.target,Me=T.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&O(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function Ei(j,T){if(!(j instanceof T))throw new TypeError("Cannot call a class as a function")}function tn(j,T){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=c()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",kt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),E}(a()),Ai=Li},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,f){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(f))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return c(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),s=f.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var f=this;function c(){f.off(i,c),s.apply(a,arguments)}return c._=s,this.on(i,c,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=a.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var rs=/["'&<>]/;Yo.exports=ns;function ns(e){var t=""+e,r=rs.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof et?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function pn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,f){s=e[i](s),o(a,f,s.done,s.value)})}}function o(i,s,a,f){Promise.resolve(f).then(function(c){i({value:c,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),f=a.next();!f.done;f=a.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(C(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{ln(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ln(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function jt(e){return e instanceof Ie||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function ln(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new xn(r,n)},t}(F);var xn=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,f=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var Sn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var Oe=new Sn(wn);var _=new F(function(e){return e.complete()});function Vt(e){return e&&C(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return C(Cr(e))?e.pop():void 0}function Te(e){return Vt(Cr(e))?e.pop():void 0}function zt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return C(e==null?void 0:e.then)}function qt(e){return C(e[ft])}function Kt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=zi();function Gt(e){return C(e==null?void 0:e[Yt])}function Bt(e){return un(this,arguments,function(){var r,n,o,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return C(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(qt(e))return Ni(e);if(pt(e))return qi(e);if(Nt(e))return Ki(e);if(Kt(e))return On(e);if(Gt(e))return Qi(e);if(Jt(e))return Yi(e)}throw Qt(e)}function Ni(e){return new F(function(t){var r=e[ft]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function qi(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?A(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Dn(function(){return new Zt}))}}function Vn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,f=a===void 0?!0:a;return function(c){var u,p,m,d=0,h=!1,v=!1,Y=function(){p==null||p.unsubscribe(),p=void 0},B=function(){Y(),u=m=void 0,h=v=!1},N=function(){var O=u;B(),O==null||O.unsubscribe()};return y(function(O,Qe){d++,!v&&!h&&Y();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,f))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,Y(),p=$r(B,o,$e),De.error($e)},complete:function(){h=!0,Y(),p=$r(B,s),De.complete()}}),U(O).subscribe(u))})(c)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),J())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Kn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>Xe(e)),V(Xe(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>rr(e)),V(rr(e)))}var Yn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Wr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Wr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ba.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Gn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Jn=typeof WeakMap!="undefined"?new WeakMap:new Yn,Xn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ga.getInstance(),n=new La(t,r,this);Jn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Xn.prototype[e]=function(){var t;return(t=Jn.get(this))[e].apply(t,arguments)}});var Aa=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:Xn}(),Zn=Aa;var eo=new x,Ca=$(()=>k(new Zn(e=>{for(let t of e)eo.next(t)}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ca.pipe(S(t=>t.observe(e)),g(t=>eo.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var to=new x,Ra=$(()=>k(new IntersectionObserver(e=>{for(let t of e)to.next(t)},{threshold:0}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function sr(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function ro(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),J())}var cr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function no(e){return cr[e].checked}function Ke(e,t){cr[e].checked!==t&&cr[e].click()}function Ue(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function ka(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ha(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function oo(){let e=b(window,"keydown").pipe(A(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:no("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),A(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!ka(n,r)}return!0}),pe());return Ha().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function io(){return new x}function ao(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)ao(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)ao(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function so(){return location.hash.substring(1)}function Dr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Pa(e){return L(b(window,"hashchange"),e).pipe(l(so),V(so()),A(t=>t.length>0),X(1))}function co(e){return Pa(e).pipe(l(t=>ce(`[id="${t}"]`)),A(t=>typeof t!="undefined"))}function Vr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function fo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function zr(e,t){return e.pipe(g(r=>r?t():_))}function ur(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Ot(()=>new Error(r.statusText)):k(r)))}function We(e,t){return ur(e,t).pipe(g(r=>r.json()),X(1))}function uo(e,t){let r=new DOMParser;return ur(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),X(1))}function pr(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(g(()=>Ot(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function po(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function lo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(po),V(po()))}function mo(){return{width:innerWidth,height:innerHeight}}function ho(){return b(window,"resize",{passive:!0}).pipe(l(mo),V(mo()))}function bo(){return G([lo(),ho()]).pipe(l(([e,t])=>({offset:e,size:t})),X(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(ee("size")),o=G([n,r]).pipe(l(()=>Xe(e)));return G([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:f,y:c}])=>({offset:{x:s.x-f,y:s.y-c+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,f,c)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:f,error:c});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Async Support

+

HTTPX offers a standard synchronous API by default, but also gives you the option of an async client if you need it.

+

Async is a concurrency model that is far more efficient than multi-threading, and can provide significant performance benefits and enable the use of long-lived network connections such as WebSockets.

+

If you're working with an async web framework then you'll also want to use an async client for sending outgoing HTTP requests.

+

Launching concurrent async tasks is far more resource efficient than spawning multiple threads. The Python interpreter should be able to comfortably handle switching between over 1000 concurrent tasks, while a sensible number of threads in a thread pool might be to enable around 10 or 20 concurrent threads.

+

API differences

+

When using async support, you need make sure to use an async connection pool class:

+
# The async variation of `httpcore.ConnectionPool`
+async with httpcore.AsyncConnectionPool() as http:
+    ...
+
+ +

Or if connecting via a proxy:

+
# The async variation of `httpcore.HTTPProxy`
+async with httpcore.AsyncHTTPProxy() as proxy:
+    ...
+
+ +

Sending requests

+

Sending requests with the async version of httpcore requires the await keyword:

+
import asyncio
+import httpcore
+
+async def main():
+    async with httpcore.AsyncConnectionPool() as http:
+        response = await http.request("GET", "https://www.example.com/")
+
+
+asyncio.run(main())
+
+ +

When including content in the request, the content must either be bytes or an async iterable yielding bytes.

+

Streaming responses

+

Streaming responses also require a slightly different interface to the sync version:

+
    +
  • with <pool>.stream(...) as responseasync with <pool>.stream() as response.
  • +
  • for chunk in response.iter_stream()async for chunk in response.aiter_stream().
  • +
  • response.read()await response.aread().
  • +
  • response.close()await response.aclose()
  • +
+

For example:

+
import asyncio
+import httpcore
+
+
+async def main():
+    async with httpcore.AsyncConnectionPool() as http:
+        async with http.stream("GET", "https://www.example.com/") as response:
+            async for chunk in response.aiter_stream():
+                print(f"Downloaded: {chunk}")
+
+
+asyncio.run(main())
+
+ +

Pool lifespans

+

When using httpcore in an async environment it is strongly recommended that you instantiate and use connection pools using the context managed style:

+
async with httpcore.AsyncConnectionPool() as http:
+    ...
+
+ +

To benefit from connection pooling it is recommended that you instantiate a single connection pool in this style, and pass it around throughout your application.

+

If you do want to use a connection pool without this style then you'll need to ensure that you explicitly close the pool once it is no longer required:

+
try:
+    http = httpcore.AsyncConnectionPool()
+    ...
+finally:
+    await http.aclose()
+
+ +

This is a little different to the threaded context, where it's okay to simply instantiate a globally available connection pool, and then allow Python's garbage collection to deal with closing any connections in the pool, once the __del__ method is called.

+

The reason for this difference is that asynchronous code is not able to run within the context of the synchronous __del__ method, so there is no way for connections to be automatically closed at the point of garbage collection. This can lead to unterminated TCP connections still remaining after the Python interpreter quits.

+

Supported environments

+

HTTPX supports either asyncio or trio as an async environment.

+

It will auto-detect which of those two to use as the backend for socket operations and concurrency primitives.

+

AsyncIO

+

AsyncIO is Python's built-in library for writing concurrent code with the async/await syntax.

+

Let's take a look at sending several outgoing HTTP requests concurrently, using asyncio:

+
import asyncio
+import httpcore
+import time
+
+
+async def download(http, year):
+    await http.request("GET", f"https://en.wikipedia.org/wiki/{year}")
+
+
+async def main():
+    async with httpcore.AsyncConnectionPool() as http:
+        started = time.time()
+        # Here we use `asyncio.gather()` in order to run several tasks concurrently...
+        tasks = [download(http, year) for year in range(2000, 2020)]
+        await asyncio.gather(*tasks)
+        complete = time.time()
+
+        for connection in http.connections:
+            print(connection)
+        print("Complete in %.3f seconds" % (complete - started))
+
+
+asyncio.run(main())
+
+ +

Trio

+

Trio is an alternative async library, designed around the the principles of structured concurrency.

+
import httpcore
+import trio
+import time
+
+
+async def download(http, year):
+    await http.request("GET", f"https://en.wikipedia.org/wiki/{year}")
+
+
+async def main():
+    async with httpcore.AsyncConnectionPool() as http:
+        started = time.time()
+        async with trio.open_nursery() as nursery:
+            for year in range(2000, 2020):
+                nursery.start_soon(download, http, year)
+        complete = time.time()
+
+        for connection in http.connections:
+            print(connection)
+        print("Complete in %.3f seconds" % (complete - started))
+
+
+trio.run(main)
+
+ +

AnyIO

+

AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio or trio. It blends in with native libraries of your chosen backend (defaults to asyncio).

+

The anyio library is designed around the the principles of structured concurrency, and brings many of the same correctness and usability benefits that Trio provides, while interoperating with existing asyncio libraries.

+
import httpcore
+import anyio
+import time
+
+
+async def download(http, year):
+    await http.request("GET", f"https://en.wikipedia.org/wiki/{year}")
+
+
+async def main():
+    async with httpcore.AsyncConnectionPool() as http:
+        started = time.time()
+        async with anyio.create_task_group() as task_group:
+            for year in range(2000, 2020):
+                task_group.start_soon(download, http, year)
+        complete = time.time()
+
+        for connection in http.connections:
+            print(connection)
+        print("Complete in %.3f seconds" % (complete - started))
+
+
+anyio.run(main)
+
+ +
+

Reference

+

httpcore.AsyncConnectionPool

+ + +
+ + +
+ +

A connection pool for making HTTP requests.

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

+connections: List[httpcore.AsyncConnectionInterface] + + + property + readonly + + +

+ +
+ +

Return a list of the connections currently in the pool.

+

For example:

+
>>> pool.connections
+[
+    <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
+    <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
+    <AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
+]
+
+
+ +
+ + + + + + + + +
+ + + +

+__init__(self, ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) + + + special + + +

+ +
+ +

A connection pool for making HTTP requests.

+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
ssl_contextOptional[ssl.SSLContext]

An SSL context to use for verifying connections. +If not specified, the default httpcore.default_ssl_context() +will be used.

None
max_connectionsOptional[int]

The maximum number of concurrent HTTP connections that +the pool should allow. Any attempt to send a request on a pool that +would exceed this amount will block until a connection is available.

10
max_keepalive_connectionsOptional[int]

The maximum number of idle HTTP connections +that will be maintained in the pool.

None
keepalive_expiryOptional[float]

The duration in seconds that an idle HTTP connection +may be maintained for before being expired from the pool.

None
http1bool

A boolean indicating if HTTP/1.1 requests should be supported +by the connection pool. Defaults to True.

True
http2bool

A boolean indicating if HTTP/2 requests should be supported by +the connection pool. Defaults to False.

False
retriesint

The maximum number of retries when trying to establish a +connection.

0
local_addressOptional[str]

Local address to connect from. Can also be used to connect +using a particular address family. Using local_address="0.0.0.0" +will connect using an AF_INET address (IPv4), while using +local_address="::" will connect using an AF_INET6 address (IPv6).

None
udsOptional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None
network_backendOptional[httpcore.AsyncNetworkBackend]

A backend instance to use for handling network I/O.

None
socket_optionsOptional[Iterable[Union[Tuple[int, int, int], Tuple[int, int, Union[bytes, bytearray]], Tuple[int, int, NoneType, int]]]]

Socket options that have to be included +in the TCP socket when the connection was established.

None
+
+ +
+ + + +
+ + + +

+aclose(self) + + + async + + +

+ +
+ +

Close any connections in the pool.

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

+handle_async_request(self, request) + + + async + + +

+ +
+ +

Send an HTTP request, and return an HTTP response.

+

This is the core implementation that is called into by .request() or .stream().

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

+response_closed(self, status) + + + async + + +

+ +
+ +

This method acts as a callback once the request/response cycle is complete.

+

It is called into from the ConnectionPoolByteStream.aclose() method.

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

httpcore.AsyncHTTPProxy

+ + +
+ + +
+ +

A connection pool that sends requests via an HTTP proxy.

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

+__init__(self, proxy_url, proxy_auth=None, proxy_headers=None, ssl_context=None, proxy_ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) + + + special + + +

+ +
+ +

A connection pool for making HTTP requests.

+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
proxy_urlUnion[httpcore.URL, bytes, str]

The URL to use when connecting to the proxy server. +For example "http://127.0.0.1:8080/".

required
proxy_authOptional[Tuple[Union[bytes, str], Union[bytes, str]]]

Any proxy authentication as a two-tuple of +(username, password). May be either bytes or ascii-only str.

None
proxy_headersUnion[Mapping[Union[bytes, str], Union[bytes, str]], Sequence[Tuple[Union[bytes, str], Union[bytes, str]]]]

Any HTTP headers to use for the proxy requests. +For example {"Proxy-Authorization": "Basic <username>:<password>"}.

None
ssl_contextOptional[ssl.SSLContext]

An SSL context to use for verifying connections. +If not specified, the default httpcore.default_ssl_context() +will be used.

None
proxy_ssl_contextOptional[ssl.SSLContext]

The same as ssl_context, but for a proxy server rather than a remote origin.

None
max_connectionsOptional[int]

The maximum number of concurrent HTTP connections that +the pool should allow. Any attempt to send a request on a pool that +would exceed this amount will block until a connection is available.

10
max_keepalive_connectionsOptional[int]

The maximum number of idle HTTP connections +that will be maintained in the pool.

None
keepalive_expiryOptional[float]

The duration in seconds that an idle HTTP connection +may be maintained for before being expired from the pool.

None
http1bool

A boolean indicating if HTTP/1.1 requests should be supported +by the connection pool. Defaults to True.

True
http2bool

A boolean indicating if HTTP/2 requests should be supported by +the connection pool. Defaults to False.

False
retriesint

The maximum number of retries when trying to establish +a connection.

0
local_addressOptional[str]

Local address to connect from. Can also be used to +connect using a particular address family. Using +local_address="0.0.0.0" will connect using an AF_INET address +(IPv4), while using local_address="::" will connect using an +AF_INET6 address (IPv6).

None
udsOptional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None
network_backendOptional[httpcore.AsyncNetworkBackend]

A backend instance to use for handling network I/O.

None
+
+ +
+ + + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/connection-pools/index.html b/connection-pools/index.html new file mode 100644 index 00000000..fbe46409 --- /dev/null +++ b/connection-pools/index.html @@ -0,0 +1,930 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Connection Pools - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Connection Pools

+

While the top-level API provides convenience functions for working with httpcore, +in practice you'll almost always want to take advantage of the connection pooling +functionality that it provides.

+

To do so, instantiate a pool instance, and use it to send requests:

+
import httpcore
+
+http = httpcore.ConnectionPool()
+r = http.request("GET", "https://www.example.com/")
+
+print(r)
+# <Response [200]>
+
+ +

Connection pools support the same .request() and .stream() APIs as described in the Quickstart.

+

We can observe the benefits of connection pooling with a simple script like so:

+
import httpcore
+import time
+
+
+http = httpcore.ConnectionPool()
+for counter in range(5):
+    started = time.time()
+    response = http.request("GET", "https://www.example.com/")
+    complete = time.time()
+    print(response, "in %.3f seconds" % (complete - started))
+
+ +

The output should demonstrate the initial request as being substantially slower than the subsequent requests:

+
<Response [200]> in {0.529} seconds
+<Response [200]> in {0.096} seconds
+<Response [200]> in {0.097} seconds
+<Response [200]> in {0.095} seconds
+<Response [200]> in {0.098} seconds
+
+ +

This is to be expected. Once we've established a connection to "www.example.com" we're able to reuse it for following requests.

+

Configuration

+

The connection pool instance is also the main point of configuration. Let's take a look at the various options that it provides:

+

SSL configuration

+
    +
  • ssl_context: An SSL context to use for verifying connections. + If not specified, the default httpcore.default_ssl_context() + will be used.
  • +
+

Pooling configuration

+
    +
  • max_connections: The maximum number of concurrent HTTP connections that the pool + should allow. Any attempt to send a request on a pool that would + exceed this amount will block until a connection is available.
  • +
  • max_keepalive_connections: The maximum number of idle HTTP connections that will + be maintained in the pool.
  • +
  • keepalive_expiry: The duration in seconds that an idle HTTP connection may be + maintained for before being expired from the pool.
  • +
+

HTTP version support

+
    +
  • http1: A boolean indicating if HTTP/1.1 requests should be supported by the connection + pool. Defaults to True.
  • +
  • http2: A boolean indicating if HTTP/2 requests should be supported by the connection + pool. Defaults to False.
  • +
+

Other options

+
    +
  • retries: The maximum number of retries when trying to establish a connection.
  • +
  • local_address: Local address to connect from. Can also be used to connect using + a particular address family. Using local_address="0.0.0.0" will + connect using an AF_INET address (IPv4), while using local_address="::" + will connect using an AF_INET6 address (IPv6).
  • +
  • uds: Path to a Unix Domain Socket to use instead of TCP sockets.
  • +
  • network_backend: A backend instance to use for handling network I/O.
  • +
  • socket_options: Socket options that have to be included in the TCP socket when the connection was established.
  • +
+

Pool lifespans

+

Because connection pools hold onto network resources, careful developers may want to ensure that instances are properly closed once they are no longer required.

+

Working with a single global instance isn't a bad idea for many use case, since the connection pool will automatically be closed when the __del__ method is called on it:

+
# This is perfectly fine for most purposes.
+# The connection pool will automatically be closed when it is garbage collected,
+# or when the Python interpreter exits.
+http = httpcore.ConnectionPool()
+
+ +

However, to be more explicit around the resource usage, we can use the connection pool within a context manager:

+
with httpcore.ConnectionPool() as http:
+    ...
+
+ +

Or else close the pool explicitly:

+
http = httpcore.ConnectionPool()
+try:
+    ...
+finally:
+    http.close()
+
+ +

Thread and task safety

+

Connection pools are designed to be thread-safe. Similarly, when using httpcore in an async context connection pools are task-safe.

+

This means that you can have a single connection pool instance shared by multiple threads.

+
+

Reference

+

httpcore.ConnectionPool

+ + +
+ + +
+ +

A connection pool for making HTTP requests.

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

+connections: List[httpcore.ConnectionInterface] + + + property + readonly + + +

+ +
+ +

Return a list of the connections currently in the pool.

+

For example:

+
>>> pool.connections
+[
+    <HTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
+    <HTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
+    <HTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
+]
+
+
+ +
+ + + + + + + + +
+ + + +

+__init__(self, ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) + + + special + + +

+ +
+ +

A connection pool for making HTTP requests.

+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
ssl_contextOptional[ssl.SSLContext]

An SSL context to use for verifying connections. +If not specified, the default httpcore.default_ssl_context() +will be used.

None
max_connectionsOptional[int]

The maximum number of concurrent HTTP connections that +the pool should allow. Any attempt to send a request on a pool that +would exceed this amount will block until a connection is available.

10
max_keepalive_connectionsOptional[int]

The maximum number of idle HTTP connections +that will be maintained in the pool.

None
keepalive_expiryOptional[float]

The duration in seconds that an idle HTTP connection +may be maintained for before being expired from the pool.

None
http1bool

A boolean indicating if HTTP/1.1 requests should be supported +by the connection pool. Defaults to True.

True
http2bool

A boolean indicating if HTTP/2 requests should be supported by +the connection pool. Defaults to False.

False
retriesint

The maximum number of retries when trying to establish a +connection.

0
local_addressOptional[str]

Local address to connect from. Can also be used to connect +using a particular address family. Using local_address="0.0.0.0" +will connect using an AF_INET address (IPv4), while using +local_address="::" will connect using an AF_INET6 address (IPv6).

None
udsOptional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None
network_backendOptional[httpcore.NetworkBackend]

A backend instance to use for handling network I/O.

None
socket_optionsOptional[Iterable[Union[Tuple[int, int, int], Tuple[int, int, Union[bytes, bytearray]], Tuple[int, int, NoneType, int]]]]

Socket options that have to be included +in the TCP socket when the connection was established.

None
+
+ +
+ + + +
+ + + +

+close(self) + + +

+ +
+ +

Close any connections in the pool.

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

+handle_request(self, request) + + +

+ +
+ +

Send an HTTP request, and return an HTTP response.

+

This is the core implementation that is called into by .request() or .stream().

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

+response_closed(self, status) + + +

+ +
+ +

This method acts as a callback once the request/response cycle is complete.

+

It is called into from the ConnectionPoolByteStream.close() method.

+ +
+ +
+ + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/connections/index.html b/connections/index.html new file mode 100644 index 00000000..cd9eb49c --- /dev/null +++ b/connections/index.html @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + Connections - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Connections

+

TODO

+
+

Reference

+

httpcore.HTTPConnection

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

+has_expired(self) + + +

+ +
+ +

Return True if the connection is in a state where it should be closed.

+

This either means that the connection is idle and it has passed the +expiry time on its keep-alive, or that server has sent an EOF.

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

+is_available(self) + + +

+ +
+ +

Return True if the connection is currently able to accept an +outgoing request.

+

An HTTP/1.1 connection will only be available if it is currently idle.

+

An HTTP/2 connection will be available so long as the stream ID space is +not yet exhausted, and the connection is not in an error state.

+

While the connection is being established we may not yet know if it is going +to result in an HTTP/1.1 or HTTP/2 connection. The connection should be +treated as being available, but might ultimately raise NewConnectionRequired +required exceptions if multiple requests are attempted over a connection +that ends up being established as HTTP/1.1.

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

+is_closed(self) + + +

+ +
+ +

Return True if the connection has been closed.

+

Used when a response is closed to determine if the connection may be +returned to the connection pool or not.

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

+is_idle(self) + + +

+ +
+ +

Return True if the connection is currently idle.

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

httpcore.HTTP11Connection

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

+has_expired(self) + + +

+ +
+ +

Return True if the connection is in a state where it should be closed.

+

This either means that the connection is idle and it has passed the +expiry time on its keep-alive, or that server has sent an EOF.

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

+is_available(self) + + +

+ +
+ +

Return True if the connection is currently able to accept an +outgoing request.

+

An HTTP/1.1 connection will only be available if it is currently idle.

+

An HTTP/2 connection will be available so long as the stream ID space is +not yet exhausted, and the connection is not in an error state.

+

While the connection is being established we may not yet know if it is going +to result in an HTTP/1.1 or HTTP/2 connection. The connection should be +treated as being available, but might ultimately raise NewConnectionRequired +required exceptions if multiple requests are attempted over a connection +that ends up being established as HTTP/1.1.

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

+is_closed(self) + + +

+ +
+ +

Return True if the connection has been closed.

+

Used when a response is closed to determine if the connection may be +returned to the connection pool or not.

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

+is_idle(self) + + +

+ +
+ +

Return True if the connection is currently idle.

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

httpcore.HTTP2Connection

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

+has_expired(self) + + +

+ +
+ +

Return True if the connection is in a state where it should be closed.

+

This either means that the connection is idle and it has passed the +expiry time on its keep-alive, or that server has sent an EOF.

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

+is_available(self) + + +

+ +
+ +

Return True if the connection is currently able to accept an +outgoing request.

+

An HTTP/1.1 connection will only be available if it is currently idle.

+

An HTTP/2 connection will be available so long as the stream ID space is +not yet exhausted, and the connection is not in an error state.

+

While the connection is being established we may not yet know if it is going +to result in an HTTP/1.1 or HTTP/2 connection. The connection should be +treated as being available, but might ultimately raise NewConnectionRequired +required exceptions if multiple requests are attempted over a connection +that ends up being established as HTTP/1.1.

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

+is_closed(self) + + +

+ +
+ +

Return True if the connection has been closed.

+

Used when a response is closed to determine if the connection may be +returned to the connection pool or not.

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

+is_idle(self) + + +

+ +
+ +

Return True if the connection is currently idle.

+ +
+ +
+ + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/exceptions/index.html b/exceptions/index.html new file mode 100644 index 00000000..3031ac4a --- /dev/null +++ b/exceptions/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + Exceptions - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Exceptions

+

The following exceptions may be raised when sending a request:

+
    +
  • httpcore.TimeoutException
      +
    • httpcore.PoolTimeout
    • +
    • httpcore.ConnectTimeout
    • +
    • httpcore.ReadTimeout
    • +
    • httpcore.WriteTimeout
    • +
    +
  • +
  • httpcore.NetworkError
      +
    • httpcore.ConnectError
    • +
    • httpcore.ReadError
    • +
    • httpcore.WriteError
    • +
    +
  • +
  • httpcore.ProtocolError
      +
    • httpcore.RemoteProtocolError
    • +
    • httpcore.LocalProtocolError
    • +
    +
  • +
  • httpcore.ProxyError
  • +
  • httpcore.UnsupportedProtocol
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/extensions/index.html b/extensions/index.html new file mode 100644 index 00000000..5db58637 --- /dev/null +++ b/extensions/index.html @@ -0,0 +1,928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Extensions - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Extensions

+

The request/response API used by httpcore is kept deliberately simple and explicit.

+

The Request and Response models are pretty slim wrappers around this core API:

+
# Pseudo-code expressing the essentials of the request/response model.
+(
+    status_code: int,
+    headers: List[Tuple(bytes, bytes)],
+    stream: Iterable[bytes]
+) = handle_request(
+    method: bytes,
+    url: URL,
+    headers: List[Tuple(bytes, bytes)],
+    stream: Iterable[bytes]
+)
+
+ +

This is everything that's needed in order to represent an HTTP exchange.

+

Well... almost.

+

There is a maxim in Computer Science that "All non-trivial abstractions, to some degree, are leaky". When an expression is leaky, it's important that it ought to at least leak only in well-defined places.

+

In order to handle cases that don't otherwise fit inside this core abstraction, httpcore requests and responses have 'extensions'. These are a dictionary of optional additional information.

+

Let's expand on our request/response abstraction...

+
# Pseudo-code expressing the essentials of the request/response model,
+# plus extensions allowing for additional API that does not fit into
+# this abstraction.
+(
+    status_code: int,
+    headers: List[Tuple(bytes, bytes)],
+    stream: Iterable[bytes],
+    extensions: dict
+) = handle_request(
+    method: bytes,
+    url: URL,
+    headers: List[Tuple(bytes, bytes)],
+    stream: Iterable[bytes],
+    extensions: dict
+)
+
+ +

Several extensions are supported both on the request:

+
r = httpcore.request(
+    "GET",
+    "https://www.example.com",
+    extensions={"timeout": {"connect": 5.0}}
+)
+
+ +

And on the response:

+
r = httpcore.request("GET", "https://www.example.com")
+
+print(r.extensions["http_version"])
+# When using HTTP/1.1 on the client side, the server HTTP response
+# could feasibly be one of b"HTTP/0.9", b"HTTP/1.0", or b"HTTP/1.1".
+
+ +

Request Extensions

+

"timeout"

+

A dictionary of str: Optional[float] timeout values.

+

May include values for 'connect', 'read', 'write', or 'pool'.

+

For example:

+
# Timeout if a connection takes more than 5 seconds to established, or if
+# we are blocked waiting on the connection pool for more than 10 seconds.
+r = httpcore.request(
+    "GET",
+    "https://www.example.com",
+    extensions={"timeout": {"connect": 5.0, "pool": 10.0}}
+)
+
+ +

"trace"

+

The trace extension allows a callback handler to be installed to monitor the internal +flow of events within httpcore. The simplest way to explain this is with an example:

+
import httpcore
+
+def log(event_name, info):
+    print(event_name, info)
+
+r = httpcore.request("GET", "https://www.example.com/", extensions={"trace": log})
+# connection.connect_tcp.started {'host': 'www.example.com', 'port': 443, 'local_address': None, 'timeout': None}
+# connection.connect_tcp.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f94d0>}
+# connection.start_tls.started {'ssl_context': <ssl.SSLContext object at 0x1093ee750>, 'server_hostname': b'www.example.com', 'timeout': None}
+# connection.start_tls.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f9450>}
+# http11.send_request_headers.started {'request': <Request [b'GET']>}
+# http11.send_request_headers.complete {'return_value': None}
+# http11.send_request_body.started {'request': <Request [b'GET']>}
+# http11.send_request_body.complete {'return_value': None}
+# http11.receive_response_headers.started {'request': <Request [b'GET']>}
+# http11.receive_response_headers.complete {'return_value': (b'HTTP/1.1', 200, b'OK', [(b'Age', b'553715'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Thu, 21 Oct 2021 17:08:42 GMT'), (b'Etag', b'"3147526947+ident"'), (b'Expires', b'Thu, 28 Oct 2021 17:08:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1DCD)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])}
+# http11.receive_response_body.started {'request': <Request [b'GET']>}
+# http11.receive_response_body.complete {'return_value': None}
+# http11.response_closed.started {}
+# http11.response_closed.complete {'return_value': None}
+
+ +

The event_name and info arguments here will be one of the following:

+
    +
  • {event_type}.{event_name}.started, <dictionary of keyword arguments>
  • +
  • {event_type}.{event_name}.complete, {"return_value": <...>}
  • +
  • {event_type}.{event_name}.failed, {"exception": <...>}
  • +
+

Note that when using the async variant of httpcore the handler function passed to "trace" must be an async def ... function.

+

The following event types are currently exposed...

+

Establishing the connection

+
    +
  • "connection.connect_tcp"
  • +
  • "connection.connect_unix_socket"
  • +
  • "connection.start_tls"
  • +
+

HTTP/1.1 events

+
    +
  • "http11.send_request_headers"
  • +
  • "http11.send_request_body"
  • +
  • "http11.receive_response"
  • +
  • "http11.receive_response_body"
  • +
  • "http11.response_closed"
  • +
+

HTTP/2 events

+
    +
  • "http2.send_connection_init"
  • +
  • "http2.send_request_headers"
  • +
  • "http2.send_request_body"
  • +
  • "http2.receive_response_headers"
  • +
  • "http2.receive_response_body"
  • +
  • "http2.response_closed"
  • +
+

"sni_hostname"

+

The server's hostname, which is used to confirm the hostname supplied by the SSL certificate.

+

For example:

+
headers = {"Host": "www.encode.io"}
+extensions = {"sni_hostname": "www.encode.io"}
+response = httpcore.request(
+    "GET",
+    "https://185.199.108.153",
+    headers=headers,
+    extensions=extensions
+)
+
+ +

Response Extensions

+

"http_version"

+

The HTTP version, as bytes. Eg. b"HTTP/1.1".

+

When using HTTP/1.1 the response line includes an explicit version, and the value of this key could feasibly be one of b"HTTP/0.9", b"HTTP/1.0", or b"HTTP/1.1".

+

When using HTTP/2 there is no further response versioning included in the protocol, and the value of this key will always be b"HTTP/2".

+

"reason_phrase"

+

The reason-phrase of the HTTP response, as bytes. For example b"OK". Some servers may include a custom reason phrase, although this is not recommended.

+

HTTP/2 onwards does not include a reason phrase on the wire.

+

When no key is included, a default based on the status code may be used.

+

"stream_id"

+

When HTTP/2 is being used the "stream_id" response extension can be accessed to determine the ID of the data stream that the response was sent on.

+

"network_stream"

+

The "network_stream" extension allows developers to handle HTTP CONNECT and Upgrade requests, by providing an API that steps outside the standard request/response model, and can directly read or write to the network.

+

The interface provided by the network stream:

+
    +
  • read(max_bytes, timeout = None) -> bytes
  • +
  • write(buffer, timeout = None)
  • +
  • close()
  • +
  • start_tls(ssl_context, server_hostname = None, timeout = None) -> NetworkStream
  • +
  • get_extra_info(info) -> Any
  • +
+

This API can be used as the foundation for working with HTTP proxies, WebSocket upgrades, and other advanced use-cases.

+

See the network backends documentation for more information on working directly with network streams.

+
CONNECT requests
+

A proxy CONNECT request using the network stream:

+
# Formulate a CONNECT request...
+#
+# This will establish a connection to 127.0.0.1:8080, and then send the following...
+#
+# CONNECT http://www.example.com HTTP/1.1
+# Host: 127.0.0.1:8080
+url = httpcore.URL(b"http", b"127.0.0.1", 8080, b"http://www.example.com")
+with httpcore.stream("CONNECT", url) as response:
+    network_stream = response.extensions["network_stream"]
+
+    # Upgrade to an SSL stream...
+    network_stream = network_stream.start_tls(
+        ssl_context=httpcore.default_ssl_context(),
+        hostname=b"www.example.com",
+    )
+
+    # Manually send an HTTP request over the network stream, and read the response...
+    #
+    # For a more complete example see the httpcore `TunnelHTTPConnection` implementation.
+    network_stream.write(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+    data = network_stream.read()
+    print(data)
+
+ +
Upgrade requests
+

Using the wsproto package to handle a websockets session:

+
import httpcore
+import wsproto
+import os
+import base64
+
+
+url = "http://127.0.0.1:8000/"
+headers = {
+    b"Connection": b"Upgrade",
+    b"Upgrade": b"WebSocket",
+    b"Sec-WebSocket-Key": base64.b64encode(os.urandom(16)),
+    b"Sec-WebSocket-Version": b"13"
+}
+with httpcore.stream("GET", url, headers=headers) as response:
+    if response.status != 101:
+        raise Exception("Failed to upgrade to websockets", response)
+
+    # Get the raw network stream.
+    network_steam = response.extensions["network_stream"]
+
+    # Write a WebSocket text frame to the stream.
+    ws_connection = wsproto.Connection(wsproto.ConnectionType.CLIENT)
+    message = wsproto.events.TextMessage("hello, world!")
+    outgoing_data = ws_connection.send(message)
+    network_steam.write(outgoing_data)
+
+    # Wait for a response.
+    incoming_data = network_steam.read(max_bytes=4096)
+    ws_connection.receive_data(incoming_data)
+    for event in ws_connection.events():
+        if isinstance(event, wsproto.events.TextMessage):
+            print("Got data:", event.data)
+
+    # Write a WebSocket close to the stream.
+    message = wsproto.events.CloseConnection(code=1000)
+    outgoing_data = ws_connection.send(message)
+    network_steam.write(outgoing_data)
+
+ +
Extra network information
+

The network stream abstraction also allows access to various low-level information that may be exposed by the underlying socket:

+
response = httpcore.request("GET", "https://www.example.com")
+network_stream = response.extensions["network_stream"]
+
+client_addr = network_stream.get_extra_info("client_addr")
+server_addr = network_stream.get_extra_info("server_addr")
+print("Client address", client_addr)
+print("Server address", server_addr)
+
+ +

The socket SSL information is also available through this interface, although you need to ensure that the underlying connection is still open, in order to access it...

+
with httpcore.stream("GET", "https://www.example.com") as response:
+    network_stream = response.extensions["network_stream"]
+
+    ssl_object = network_stream.get_extra_info("ssl_object")
+    print("TLS version", ssl_object.version())
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/http2/index.html b/http2/index.html new file mode 100644 index 00000000..edd21614 --- /dev/null +++ b/http2/index.html @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + HTTP/2 - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

HTTP/2

+

HTTP/2 is a major new iteration of the HTTP protocol, that provides a more efficient transport, with potential performance benefits. HTTP/2 does not change the core semantics of the request or response, but alters the way that data is sent to and from the server.

+

Rather than the text format that HTTP/1.1 uses, HTTP/2 is a binary format. The binary format provides full request and response multiplexing, and efficient compression of HTTP headers. The stream multiplexing means that where HTTP/1.1 requires one TCP stream for each concurrent request, HTTP/2 allows a single TCP stream to handle multiple concurrent requests.

+

HTTP/2 also provides support for functionality such as response prioritization, and server push.

+

For a comprehensive guide to HTTP/2 you may want to check out "HTTP2 Explained".

+

Enabling HTTP/2

+

When using the httpcore client, HTTP/2 support is not enabled by default, because HTTP/1.1 is a mature, battle-hardened transport layer, and our HTTP/1.1 implementation may be considered the more robust option at this point in time. It is possible that a future version of httpcore may enable HTTP/2 support by default.

+

If you're issuing highly concurrent requests you might want to consider trying out our HTTP/2 support. You can do so by first making sure to install the optional HTTP/2 dependencies...

+
$ pip install httpcore[http2]
+
+ +

And then instantiating a connection pool with HTTP/2 support enabled:

+
import httpcore
+
+pool = httpcore.ConnectionPool(http2=True)
+
+ +

We can take a look at the difference in behaviour by issuing several outgoing requests in parallel.

+

Start out by using a standard HTTP/1.1 connection pool:

+
import httpcore
+import concurrent.futures
+import time
+
+
+def download(http, year):
+    http.request("GET", f"https://en.wikipedia.org/wiki/{year}")
+
+
+def main():
+    with httpcore.ConnectionPool() as http:
+        started = time.time()
+        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as threads:
+            for year in range(2000, 2020):
+                threads.submit(download, http, year)
+        complete = time.time()
+
+        for connection in http.connections:
+            print(connection)
+        print("Complete in %.3f seconds" % (complete - started))
+
+
+main()
+
+ +

If you run this with an HTTP/1.1 connection pool, you ought to see output similar to the following:

+
<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 2]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 3]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 6]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 5]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,
+<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>
+Complete in 0.586 seconds
+
+ +

We can see that the connection pool required a number of connections in order to handle the parallel requests.

+

If we now upgrade our connection pool to support HTTP/2:

+
with httpcore.ConnectionPool(http2=True) as http:
+    ...
+
+ +

And run the same script again, we should end up with something like this:

+
<HTTPConnection ['https://en.wikipedia.org:443', HTTP/2, IDLE, Request Count: 20]>
+Complete in 0.573 seconds
+
+ +

All of our requests have been handled over a single connection.

+

Switching to HTTP/2 should not necessarily be considered an "upgrade". It is more complex, and requires more computational power, and so particularly in an interpreted language like Python it could be slower in some instances. Moreover, utilising multiple connections may end up connecting to multiple hosts, and could sometimes appear faster to the client, at the cost of requiring more server resources. Enabling HTTP/2 is most likely to be beneficial if you are sending requests in high concurrency, and may often be more well suited to an async context, rather than multi-threading.

+

Inspecting the HTTP version

+

Enabling HTTP/2 support on the client does not necessarily mean that your requests and responses will be transported over HTTP/2, since both the client and the server need to support HTTP/2. If you connect to a server that only supports HTTP/1.1 the client will use a standard HTTP/1.1 connection instead.

+

You can determine which version of the HTTP protocol was used by examining the "http_version" response extension.

+
import httpcore
+
+pool = httpcore.ConnectionPool(http2=True)
+response = pool.request("GET", "https://www.example.com/")
+
+# Should be one of b"HTTP/2", b"HTTP/1.1", b"HTTP/1.0", or b"HTTP/0.9".
+print(response.extensions["http_version"])
+
+ +

See the extensions documentation for more details.

+

HTTP/2 negotiation

+

Robust servers need to support both HTTP/2 and HTTP/1.1 capable clients, and so need some way to "negotiate" with the client which protocol version will be used.

+

HTTP/2 over HTTPS

+

Generally the method used is for the server to advertise if it has HTTP/2 support during the part of the SSL connection handshake. This is known as ALPN - "Application Layer Protocol Negotiation".

+

Most browsers only provide HTTP/2 support over HTTPS connections, and this is also the default behaviour that httpcore provides. If you enable HTTP/2 support you should still expect to see HTTP/1.1 connections for any http:// URLs.

+

HTTP/2 over HTTP

+

Servers can optionally also support HTTP/2 over HTTP by supporting the Upgrade: h2c header.

+

This mechanism is not supported by httpcore. It requires an additional round-trip between the client and server, and also requires any request body to be sent twice.

+

Prior Knowledge

+

If you know in advance that the server you are communicating with will support HTTP/2, then you can enforce that the client uses HTTP/2, without requiring either ALPN support or an HTTP Upgrade: h2c header.

+

This is managed by disabling HTTP/1.1 support on the connection pool:

+
pool = httpcore.ConnectionPool(http1=False, http2=True)
+
+ +

Request & response headers

+

Because HTTP/2 frames the requests and responses somewhat differently to HTTP/1.1, there is a difference in some of the headers that are used.

+

In order for the httpcore library to support both HTTP/1.1 and HTTP/2 transparently, the HTTP/1.1 style is always used throughout the API. Any differences in header styles are only mapped onto HTTP/2 at the internal network layer.

+

Request headers

+

The following pseudo-headers are used by HTTP/2 in the request:

+
    +
  • :method - The request method.
  • +
  • :path - Taken from the URL of the request.
  • +
  • :authority - Equivalent to the Host header in HTTP/1.1. In httpcore this is represented using the request Host header, which is automatically populated from the request URL if no Host header is explicitly included.
  • +
  • :scheme - Taken from the URL of the request.
  • +
+

These pseudo-headers are included in httpcore as part of the request.method and request.url attributes, and through the request.headers["Host"] header. They are not exposed directly by their psuedo-header names.

+

The one other difference to be aware of is the Transfer-Encoding: chunked header.

+

In HTTP/2 this header is never used, since streaming data is framed using a different mechanism.

+

In httpcore the Transfer-Encoding: chunked header is always used to represent the presence of a streaming body on the request, and is automatically populated if required. However the header is only sent if the underlying connection ends up being HTTP/1.1, and is omitted if the underlying connection ends up being HTTP/2.

+

Response headers

+

The following pseudo-header is used by HTTP/2 in the response:

+
    +
  • :status - The response status code.
  • +
+

In httpcore this is represented by the response.status attribute, rather than being exposed as a psuedo-header.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..825bbd55 --- /dev/null +++ b/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

HTTPCore

+

Test Suite +Package version

+
+

Do one thing, and do it well.

+
+

The HTTP Core package provides a minimal low-level HTTP client, which does +one thing only. Sending HTTP requests.

+

It does not provide any high level model abstractions over the API, +does not handle redirects, multipart uploads, building authentication headers, +transparent HTTP caching, URL parsing, session cookie handling, +content or charset decoding, handling JSON, environment based configuration +defaults, or any of that Jazz.

+

Some things HTTP Core does do:

+
    +
  • Sending HTTP requests.
  • +
  • Thread-safe / task-safe connection pooling.
  • +
  • HTTP(S) proxy & SOCKS proxy support.
  • +
  • Supports HTTP/1.1 and HTTP/2.
  • +
  • Provides both sync and async interfaces.
  • +
  • Async backend support for asyncio and trio.
  • +
+

Requirements

+

Python 3.8+

+

Installation

+

For HTTP/1.1 only support, install with:

+
$ pip install httpcore
+
+ +

For HTTP/1.1 and HTTP/2 support, install with:

+
$ pip install httpcore[http2]
+
+ +

For SOCKS proxy support, install with:

+
$ pip install httpcore[socks]
+
+ +

Example

+

Let's check we're able to send HTTP requests:

+
import httpcore
+
+response = httpcore.request("GET", "https://www.example.com/")
+
+print(response)
+# <Response [200]>
+print(response.status)
+# 200
+print(response.headers)
+# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...]
+print(response.content)
+# b'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>\n\n<meta charset="utf-8"/>\n ...'
+
+ +

Ready to get going?

+

Head over to the quickstart documentation.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/logging/index.html b/logging/index.html new file mode 100644 index 00000000..c08473ba --- /dev/null +++ b/logging/index.html @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Logging - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Logging

+

If you need to inspect the internal behaviour of httpcore, you can use Python's standard logging to output debug level information.

+

For example, the following configuration...

+
import logging
+import httpcore
+
+logging.basicConfig(
+    format="%(levelname)s [%(asctime)s] %(name)s - %(message)s",
+    datefmt="%Y-%m-%d %H:%M:%S",
+    level=logging.DEBUG
+)
+
+httpcore.request('GET', 'https://www.example.com')
+
+ +

Will send debug level output to the console, or wherever stdout is directed too...

+
DEBUG [2023-01-09 14:44:00] httpcore.connection - connect_tcp.started host='www.example.com' port=443 local_address=None timeout=None
+DEBUG [2023-01-09 14:44:00] httpcore.connection - connect_tcp.complete return_value=<httpcore.backends.sync.SyncStream object at 0x109ba6610>
+DEBUG [2023-01-09 14:44:00] httpcore.connection - start_tls.started ssl_context=<ssl.SSLContext object at 0x109e427b0> server_hostname='www.example.com' timeout=None
+DEBUG [2023-01-09 14:44:00] httpcore.connection - start_tls.complete return_value=<httpcore.backends.sync.SyncStream object at 0x109e8b050>
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_headers.started request=<Request [b'GET']>
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_headers.complete
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_body.started request=<Request [b'GET']>
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_body.complete
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_headers.started request=<Request [b'GET']>
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Age', b'572646'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Mon, 09 Jan 2023 14:44:00 GMT'), (b'Etag', b'"3147526947+ident"'), (b'Expires', b'Mon, 16 Jan 2023 14:44:00 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1D18)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_body.started request=<Request [b'GET']>
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_body.complete
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - response_closed.started
+DEBUG [2023-01-09 14:44:00] httpcore.http11 - response_closed.complete
+DEBUG [2023-01-09 14:44:00] httpcore.connection - close.started
+DEBUG [2023-01-09 14:44:00] httpcore.connection - close.complete
+
+ +

The exact formatting of the debug logging may be subject to change across different versions of httpcore. If you need to rely on a particular format it is recommended that you pin installation of the package to a fixed version.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/network-backends/index.html b/network-backends/index.html new file mode 100644 index 00000000..2356f439 --- /dev/null +++ b/network-backends/index.html @@ -0,0 +1,878 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Network Backends - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Network Backends

+

The API layer at which httpcore interacts with the network is described as the network backend. Various backend implementations are provided, allowing httpcore to handle networking in different runtime contexts.

+

Working with network backends

+

The default network backend

+

Typically you won't need to specify a network backend, as a default will automatically be selected. However, understanding how the network backends fit in may be useful if you want to better understand the underlying architecture. Let's start by seeing how we can explicitly select the network backend.

+

First we're making a standard HTTP request, using a connection pool:

+
import httpcore
+
+with httpcore.ConnectionPool() as http:
+    response = http.request('GET', 'https://www.example.com')
+    print(response)
+
+ +

We can also have the same behavior, but be explicit with our selection of the network backend:

+
import httpcore
+
+network_backend = httpcore.SyncBackend()
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+    response = http.request('GET', 'https://www.example.com')
+    print(response)
+
+ +

The httpcore.SyncBackend() implementation handles the opening of TCP connections, and operations on the socket stream, such as reading, writing, and closing the connection.

+

We can get a better understanding of this by using a network backend to send a basic HTTP/1.1 request directly:

+
import httpcore
+
+# Create an SSL context using 'certifi' for the certificates.
+ssl_context = httpcore.default_ssl_context()
+
+# A basic HTTP/1.1 request as a plain bytestring.
+request = b'\r\n'.join([
+    b'GET / HTTP/1.1',
+    b'Host: www.example.com',
+    b'Accept: */*',
+    b'Connection: close',
+    b''
+])
+
+# Open a TCP stream and upgrade it to SSL.
+network_backend = httpcore.SyncBackend()
+network_stream = network_backend.connect_tcp("www.example.com", 443)
+network_stream = network_stream.start_tls(ssl_context, server_hostname="www.example.com")
+
+# Send the HTTP request.
+network_stream.write(request)
+
+# Read the HTTP response.
+while True:
+    response = network_stream.read(max_bytes=4096)
+    if response == b'':
+        break
+    print(response)
+
+# The output should look something like this:
+#
+# b'HTTP/1.1 200 OK\r\nAge: 600005\r\n [...] Content-Length: 1256\r\nConnection: close\r\n\r\n'
+# b'<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title> [...] </html>\n'
+
+ +

Async network backends

+

If we're working with an async codebase, then we need to select a different backend.

+

The httpcore.AnyIOBackend is suitable for usage if you're running under asyncio. This is a networking backend implemented using the anyio package.

+
import httpcore
+import asyncio
+
+async def main():
+    network_backend = httpcore.AnyIOBackend()
+    async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:
+        response = await http.request('GET', 'https://www.example.com')
+        print(response)
+
+asyncio.run(main())
+
+ +

The AnyIOBackend will work when running under either asyncio or trio. However, if you're working with async using the trio framework, then we recommend using the httpcore.TrioBackend.

+

This will give you the same kind of networking behavior you'd have using AnyIOBackend, but there will be a little less indirection so it will be marginally more efficient and will present cleaner tracebacks in error cases.

+
import httpcore
+import trio
+
+async def main():
+    network_backend = httpcore.TrioBackend()
+    async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:
+        response = await http.request('GET', 'https://www.example.com')
+        print(response)
+
+trio.run(main)
+
+ +

Mock network backends

+

There are also mock network backends available that can be useful for testing purposes. +These backends accept a list of bytes, and return network stream interfaces that return those byte streams.

+

Here's an example of mocking a simple HTTP/1.1 response...

+
import httpcore
+
+network_backend = httpcore.MockBackend([
+    b"HTTP/1.1 200 OK\r\n",
+    b"Content-Type: plain/text\r\n",
+    b"Content-Length: 13\r\n",
+    b"\r\n",
+    b"Hello, world!",
+])
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+    response = http.request("GET", "https://example.com/")
+    print(response.extensions['http_version'])
+    print(response.status)
+    print(response.content)
+
+ +

Mocking a HTTP/2 response is more complex, since it uses a binary format...

+
import hpack
+import hyperframe.frame
+import httpcore
+
+content = [
+    hyperframe.frame.SettingsFrame().serialize(),
+    hyperframe.frame.HeadersFrame(
+        stream_id=1,
+        data=hpack.Encoder().encode(
+            [
+                (b":status", b"200"),
+                (b"content-type", b"plain/text"),
+            ]
+        ),
+        flags=["END_HEADERS"],
+    ).serialize(),
+    hyperframe.frame.DataFrame(
+        stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]
+    ).serialize(),
+]
+# Note that we instantiate the mock backend with an `http2=True` argument.
+# This ensures that the mock network stream acts as if the `h2` ALPN flag has been set,
+# and causes the connection pool to interact with the connection using HTTP/2.
+network_backend = httpcore.MockBackend(content, http2=True)
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+    response = http.request("GET", "https://example.com/")
+    print(response.extensions['http_version'])
+    print(response.status)
+    print(response.content)
+
+ +

Custom network backends

+

The base interface for network backends is provided as public API, allowing you to implement custom networking behavior.

+

You can use this to provide advanced networking functionality such as:

+
    +
  • Network recording / replay.
  • +
  • In-depth debug tooling.
  • +
  • Handling non-standard SSL or DNS requirements.
  • +
+

Here's an example that records the network response to a file on disk:

+
import httpcore
+
+
+class RecordingNetworkStream(httpcore.NetworkStream):
+    def __init__(self, record_file, stream):
+        self.record_file = record_file
+        self.stream = stream
+
+    def read(self, max_bytes, timeout=None):
+        data = self.stream.read(max_bytes, timeout=timeout)
+        self.record_file.write(data)
+        return data
+
+    def write(self, buffer, timeout=None):
+        self.stream.write(buffer, timeout=timeout)
+
+    def close(self) -> None:
+        self.stream.close()
+
+    def start_tls(
+        self,
+        ssl_context,
+        server_hostname=None,
+        timeout=None,
+    ):
+        self.stream = self.stream.start_tls(
+            ssl_context, server_hostname=server_hostname, timeout=timeout
+        )
+        return self
+
+    def get_extra_info(self, info):
+        return self.stream.get_extra_info(info)
+
+
+class RecordingNetworkBackend(httpcore.NetworkBackend):
+    """
+    A custom network backend that records network responses.
+    """
+    def __init__(self, record_file):
+        self.record_file = record_file
+        self.backend = httpcore.SyncBackend()
+
+    def connect_tcp(
+        self,
+        host,
+        port,
+        timeout=None,
+        local_address=None,
+        socket_options=None,
+    ):
+        # Note that we're only using a single record file here,
+        # so even if multiple connections are opened the network
+        # traffic will all write to the same file.
+
+        # An alternative implementation might automatically use
+        # a new file for each opened connection.
+        stream = self.backend.connect_tcp(
+            host,
+            port,
+            timeout=timeout,
+            local_address=local_address,
+            socket_options=socket_options
+        )
+        return RecordingNetworkStream(self.record_file, stream)
+
+
+# Once you make the request, the raw HTTP/1.1 response will be available
+# in the 'network-recording' file.
+#
+# Try switching to `http2=True` to see the difference when recording HTTP/2 binary network traffic,
+# or add `headers={'Accept-Encoding': 'gzip'}` to see HTTP content compression.
+with open("network-recording", "wb") as record_file:
+    network_backend = RecordingNetworkBackend(record_file)
+    with httpcore.ConnectionPool(network_backend=network_backend) as http:
+        response = http.request("GET", "https://www.example.com/")
+        print(response)
+
+ +
+

Reference

+

Networking Backends

+
    +
  • httpcore.SyncBackend
  • +
  • httpcore.AnyIOBackend
  • +
  • httpcore.TrioBackend
  • +
+

Mock Backends

+
    +
  • httpcore.MockBackend
  • +
  • httpcore.MockStream
  • +
  • httpcore.AsyncMockBackend
  • +
  • httpcore.AsyncMockStream
  • +
+

Base Interface

+
    +
  • httpcore.NetworkBackend
  • +
  • httpcore.NetworkStream
  • +
  • httpcore.AsyncNetworkBackend
  • +
  • httpcore.AsyncNetworkStream
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..0186fa1801f8ec7040cf888638887014ab0459b6 GIT binary patch literal 461 zcmV;;0W$t0AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkNR8&wy zZ*pY{BOq2~a&u{KZaN?^E-)@I3L_v?Xk{RBWo=<;Ze(S0Aa78b#rNMXCQiPX<{x4c-p0uKW~FD5XJX?3Z!<03dU|#mku4GD4i|W zT!mG~c6_D^-@Xt?NWuKE8Nv5^clVxjg|(U~1Ia5^lD#P@fmyEPPAL)T>V*l47^M+y zo}V9|qWGbhK@xWR#0IC<8V{NfF1aP7L62Yym3zRH355`Z7F6Z}$fcYZIF*1lNXP){ zdUYU|S@Lxn?}x)~XVke`W%#Xny<5|<_Ho6!e!^LBB3wTW=g^*GjB@tlyR=<7`w7jZ zy`a>j*sHlCnP=)Xkic1UGue4TfFq&5lneSLAXs5`^<|vrUcJ*a#I$=V(6)aPfyNCr zysaAwc$$}mc$aOqF5B*0w$Zq3lW(Kjtm_{fCQIEn&JEoiq3=?**`(2_twj46(!71#(*1M);orE10($-d1JF(` D=m^vy literal 0 HcmV?d00001 diff --git a/proxies/index.html b/proxies/index.html new file mode 100644 index 00000000..a22b363b --- /dev/null +++ b/proxies/index.html @@ -0,0 +1,785 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Proxies - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Proxies

+

The httpcore package provides support for HTTP proxies, using either "HTTP Forwarding" or "HTTP Tunnelling". Forwarding is a proxy mechanism for sending requests to http URLs via an intermediate proxy. Tunnelling is a proxy mechanism for sending requests to https URLs via an intermediate proxy.

+

Sending requests via a proxy is very similar to sending requests using a standard connection pool:

+
import httpcore
+
+proxy = httpcore.HTTPProxy(proxy_url="http://127.0.0.1:8080/")
+r = proxy.request("GET", "https://www.example.com/")
+
+print(r)
+# <Response [200]>
+
+ +

You can test the httpcore proxy support, using the Python proxy.py tool:

+
$ pip install proxy.py
+$ proxy --hostname 127.0.0.1 --port 8080
+
+ +

Requests will automatically use either forwarding or tunnelling, depending on if the scheme is http or https.

+

Authentication

+

Proxy authentication can be included in the initial configuration:

+
import httpcore
+
+# A `Proxy-Authorization` header will be included on the initial proxy connection.
+proxy = httpcore.HTTPProxy(
+    proxy_url="http://127.0.0.1:8080/",
+    proxy_auth=("<username>", "<password>")
+)
+
+ +

Custom headers can also be included:

+
import httpcore
+import base64
+
+# Construct and include a `Proxy-Authorization` header.
+auth = base64.b64encode(b"<username>:<password>")
+proxy = httpcore.HTTPProxy(
+    proxy_url="http://127.0.0.1:8080/",
+    proxy_headers={"Proxy-Authorization": b"Basic " + auth}
+)
+
+ +

Proxy SSL

+

The httpcore package also supports HTTPS proxies for http and https destinations.

+

HTTPS proxies can be used in the same way that HTTP proxies are.

+
proxy = httpcore.HTTPProxy(proxy_url="https://127.0.0.1:8080/")
+
+ +

Also, when using HTTPS proxies, you may need to configure the SSL context, which you can do with the proxy_ssl_context argument.

+
import ssl
+import httpcore
+
+proxy_ssl_context = ssl.create_default_context()
+proxy_ssl_context.check_hostname = False
+
+proxy = httpcore.HTTPProxy('https://127.0.0.1:8080/', proxy_ssl_context=proxy_ssl_context)
+
+ +

It is important to note that the ssl_context argument is always used for the remote connection, and the proxy_ssl_context argument is always used for the proxy connection.

+

HTTP Versions

+

If you use proxies, keep in mind that the httpcore package only supports proxies to HTTP/1.1 servers.

+

SOCKS proxy support

+

The httpcore package also supports proxies using the SOCKS5 protocol.

+

Make sure to install the optional dependancy using pip install httpcore[socks].

+

The SOCKSProxy class should be using instead of a standard connection pool:

+
import httpcore
+
+# Note that the SOCKS port is 1080.
+proxy = httpcore.SOCKSProxy(proxy_url="socks5://127.0.0.1:1080/")
+r = proxy.request("GET", "https://www.example.com/")
+
+ +

Authentication via SOCKS is also supported:

+
import httpcore
+
+proxy = httpcore.SOCKSProxy(
+    proxy_url="socks5://127.0.0.1:8080/",
+    proxy_auth=("<username>", "<password>")
+)
+r = proxy.request("GET", "https://www.example.com/")
+
+ +
+

Reference

+

httpcore.HTTPProxy

+ + +
+ + +
+ +

A connection pool that sends requests via an HTTP proxy.

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

+__init__(self, proxy_url, proxy_auth=None, proxy_headers=None, ssl_context=None, proxy_ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) + + + special + + +

+ +
+ +

A connection pool for making HTTP requests.

+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
proxy_urlUnion[httpcore.URL, bytes, str]

The URL to use when connecting to the proxy server. +For example "http://127.0.0.1:8080/".

required
proxy_authOptional[Tuple[Union[bytes, str], Union[bytes, str]]]

Any proxy authentication as a two-tuple of +(username, password). May be either bytes or ascii-only str.

None
proxy_headersUnion[Mapping[Union[bytes, str], Union[bytes, str]], Sequence[Tuple[Union[bytes, str], Union[bytes, str]]]]

Any HTTP headers to use for the proxy requests. +For example {"Proxy-Authorization": "Basic <username>:<password>"}.

None
ssl_contextOptional[ssl.SSLContext]

An SSL context to use for verifying connections. +If not specified, the default httpcore.default_ssl_context() +will be used.

None
proxy_ssl_contextOptional[ssl.SSLContext]

The same as ssl_context, but for a proxy server rather than a remote origin.

None
max_connectionsOptional[int]

The maximum number of concurrent HTTP connections that +the pool should allow. Any attempt to send a request on a pool that +would exceed this amount will block until a connection is available.

10
max_keepalive_connectionsOptional[int]

The maximum number of idle HTTP connections +that will be maintained in the pool.

None
keepalive_expiryOptional[float]

The duration in seconds that an idle HTTP connection +may be maintained for before being expired from the pool.

None
http1bool

A boolean indicating if HTTP/1.1 requests should be supported +by the connection pool. Defaults to True.

True
http2bool

A boolean indicating if HTTP/2 requests should be supported by +the connection pool. Defaults to False.

False
retriesint

The maximum number of retries when trying to establish +a connection.

0
local_addressOptional[str]

Local address to connect from. Can also be used to +connect using a particular address family. Using +local_address="0.0.0.0" will connect using an AF_INET address +(IPv4), while using local_address="::" will connect using an +AF_INET6 address (IPv6).

None
udsOptional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None
network_backendOptional[httpcore.NetworkBackend]

A backend instance to use for handling network I/O.

None
+
+ +
+ + + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/quickstart/index.html b/quickstart/index.html new file mode 100644 index 00000000..95a85301 --- /dev/null +++ b/quickstart/index.html @@ -0,0 +1,818 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Quickstart - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Quickstart

+

For convenience, the httpcore package provides a couple of top-level functions that you can use for sending HTTP requests. You probably don't want to integrate against functions if you're writing a library that uses httpcore, but you might find them useful for testing httpcore from the command-line, or if you're writing a simple script that doesn't require any of the connection pooling or advanced configuration that httpcore offers.

+

Sending a request

+

We'll start off by sending a request...

+
import httpcore
+
+response = httpcore.request("GET", "https://www.example.com/")
+
+print(response)
+# <Response [200]>
+print(response.status)
+# 200
+print(response.headers)
+# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...]
+print(response.content)
+# b'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>\n\n<meta charset="utf-8"/>\n ...'
+
+ +

Request headers

+

Request headers may be included either in a dictionary style, or as a list of two-tuples.

+
import httpcore
+import json
+
+headers = {'User-Agent': 'httpcore'}
+r = httpcore.request('GET', 'https://httpbin.org/headers', headers=headers)
+
+print(json.loads(r.content))
+# {
+#     'headers': {
+#         'Host': 'httpbin.org',
+#         'User-Agent': 'httpcore',
+#         'X-Amzn-Trace-Id': 'Root=1-616ff5de-5ea1b7e12766f1cf3b8e3a33'
+#     }
+# }
+
+ +

The keys and values may either be provided as strings or as bytes. Where strings are provided they may only contain characters within the ASCII range chr(0) - chr(127). To include characters outside this range you must deal with any character encoding explicitly, and pass bytes as the header keys/values.

+

The Host header will always be automatically included in any outgoing request, as it is strictly required to be present by the HTTP protocol.

+

Note that the X-Amzn-Trace-Id header shown in the example above is not an outgoing request header, but has been added by a gateway server.

+

Request body

+

A request body can be included either as bytes...

+
import httpcore
+import json
+
+r = httpcore.request('POST', 'https://httpbin.org/post', content=b'Hello, world')
+
+print(json.loads(r.content))
+# {
+#     'args': {},
+#     'data': 'Hello, world',
+#     'files': {},
+#     'form': {},
+#     'headers': {
+#         'Host': 'httpbin.org',
+#         'Content-Length': '12',
+#         'X-Amzn-Trace-Id': 'Root=1-61700258-00e338a124ca55854bf8435f'
+#     },
+#     'json': None,
+#     'origin': '68.41.35.196',
+#     'url': 'https://httpbin.org/post'
+# }
+
+ +

Or as an iterable that returns bytes...

+
import httpcore
+import json
+
+with open("hello-world.txt", "rb") as input_file:
+    r = httpcore.request('POST', 'https://httpbin.org/post', content=input_file)
+
+print(json.loads(r.content))
+# {
+#     'args': {},
+#     'data': 'Hello, world',
+#     'files': {},
+#     'form': {},
+#     'headers': {
+#         'Host': 'httpbin.org',
+#         'Transfer-Encoding': 'chunked',
+#         'X-Amzn-Trace-Id': 'Root=1-61700258-00e338a124ca55854bf8435f'
+#     },
+#     'json': None,
+#     'origin': '68.41.35.196',
+#     'url': 'https://httpbin.org/post'
+# }
+
+ +

When a request body is included, either a Content-Length header or a Transfer-Encoding: chunked header will be automatically included.

+

The Content-Length header is used when passing bytes, and indicates an HTTP request with a body of a pre-determined length.

+

The Transfer-Encoding: chunked header is the mechanism that HTTP/1.1 uses for sending HTTP request bodies without a pre-determined length.

+

Streaming responses

+

When using the httpcore.request() function, the response body will automatically be read to completion, and made available in the response.content attribute.

+

Sometimes you may be dealing with large responses and not want to read the entire response into memory. The httpcore.stream() function provides a mechanism for sending a request and dealing with a streaming response:

+
import httpcore
+
+with httpcore.stream('GET', 'https://example.com') as response:
+    for chunk in response.iter_stream():
+        print(f"Downloaded: {chunk}")
+
+ +

Here's a more complete example that demonstrates downloading a response:

+
import httpcore
+
+with httpcore.stream('GET', 'https://speed.hetzner.de/100MB.bin') as response:
+    with open("download.bin", "wb") as output_file:
+        for chunk in response.iter_stream():
+            output_file.write(chunk)
+
+ +

The httpcore.stream() API also allows you to conditionally read the response...

+
import httpcore
+
+with httpcore.stream('GET', 'https://example.com') as response:
+    content_length = [int(v) for k, v in response.headers if k.lower() == b'content-length'][0]
+    if content_length > 100_000_000:
+        raise Exception("Response too large.")
+    response.read()  # `response.content` is now available.
+
+ +
+

Reference

+

httpcore.request()

+ + +
+ + +
+ +

Sends an HTTP request, returning the response.

+
response = httpcore.request("GET", "https://www.example.com/")
+
+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
methodUnion[bytes, str]

The HTTP method for the request. Typically one of "GET", +"OPTIONS", "HEAD", "POST", "PUT", "PATCH", or "DELETE".

required
urlUnion[httpcore.URL, bytes, str]

The URL of the HTTP request. Either as an instance of httpcore.URL, +or as str/bytes.

required
headersUnion[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers. Either as a dictionary of str/bytes, +or as a list of two-tuples of str/bytes.

None
contentUnion[bytes, Iterator[bytes]]

The content of the request body. Either as bytes, +or as a bytes iterator.

None
extensionsOptional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the request. +Possible keys include "timeout".

None
+

Returns:

+ + + + + + + + + + + + + +
TypeDescription
Response

An instance of httpcore.Response.

+
+ +
+ +

httpcore.stream()

+ + +
+ + +
+ +

Sends an HTTP request, returning the response within a content manager.

+
with httpcore.stream("GET", "https://www.example.com/") as response:
+    ...
+
+ +

When using the stream() function, the body of the response will not be +automatically read. If you want to access the response body you should +either use content = response.read(), or for chunk in response.iter_content().

+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
methodUnion[bytes, str]

The HTTP method for the request. Typically one of "GET", +"OPTIONS", "HEAD", "POST", "PUT", "PATCH", or "DELETE".

required
urlUnion[httpcore.URL, bytes, str]

The URL of the HTTP request. Either as an instance of httpcore.URL, +or as str/bytes.

required
headersUnion[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers. Either as a dictionary of str/bytes, +or as a list of two-tuples of str/bytes.

None
contentUnion[bytes, Iterator[bytes]]

The content of the request body. Either as bytes, +or as a bytes iterator.

None
extensionsOptional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the request. +Possible keys include "timeout".

None
+

Returns:

+ + + + + + + + + + + + + +
TypeDescription
Iterator[httpcore.Response]

An instance of httpcore.Response.

+
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/requests-responses-urls/index.html b/requests-responses-urls/index.html new file mode 100644 index 00000000..872471c8 --- /dev/null +++ b/requests-responses-urls/index.html @@ -0,0 +1,845 @@ + + + + + + + + + + + + + + + + + + + + + + Requests, Responses, and URLs - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Requests, Responses, and URLs

+

TODO

+

Requests

+

Request instances in httpcore are deliberately simple, and only include the essential information required to represent an HTTP request.

+

Properties on the request are plain byte-wise representations.

+
>>> request = httpcore.Request("GET", "https://www.example.com/")
+>>> request.method
+b"GET"
+>>> request.url
+httpcore.URL(scheme=b"https", host=b"www.example.com", port=None, target=b"/")
+>>> request.headers
+[(b'Host', b'www.example.com')]
+>>> request.stream
+<httpcore.ByteStream [0 bytes]>
+
+ +

The interface is liberal in the types that it accepts, but specific in the properties that it uses to represent them. For example, headers may be specified as a dictionary of strings, but internally are represented as a list of (byte, byte) tuples.

+

```python

+
+
+
+

headers = {"User-Agent": "custom"} +request = httpcore.Request("GET", "https://www.example.com/", headers=headers) +request.headers +[(b'Host', b'www.example.com'), (b"User-Agent", b"custom")]

+
+
+
+

Responses

+

...

+

URLs

+

...

+
+

Reference

+

httpcore.Request

+ + +
+ + +
+ +

An HTTP request.

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

+__init__(self, method, url, *, headers=None, content=None, extensions=None) + + + special + + +

+ +
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
methodUnion[bytes, str]

The HTTP request method, either as a string or bytes. +For example: GET.

required
urlUnion[httpcore.URL, bytes, str]

The request URL, either as a URL instance, or as a string or bytes. +For example: "https://www.example.com".

required
headersUnion[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers.

None
contentUnion[bytes, Iterable[bytes], AsyncIterable[bytes]]

The content of the response body.

None
extensionsOptional[MutableMapping[str, Any]]

A dictionary of optional extra information included on +the request. Possible keys include "timeout", and "trace".

None
+
+ +
+ + + + + + +
+ +
+ +
+ +

httpcore.Response

+ + +
+ + +
+ +

An HTTP response.

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

+__init__(self, status, *, headers=None, content=None, extensions=None) + + + special + + +

+ +
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
statusint

The HTTP status code of the response. For example 200.

required
headersUnion[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP response headers.

None
contentUnion[bytes, Iterable[bytes], AsyncIterable[bytes]]

The content of the response body.

None
extensionsOptional[MutableMapping[str, Any]]

A dictionary of optional extra information included on +the responseself.Possible keys include "http_version", +"reason_phrase", and "network_stream".

None
+
+ +
+ + + + + + + + + + + + +
+ +
+ +
+ +

httpcore.URL

+ + +
+ + +
+ +

Represents the URL against which an HTTP request may be made.

+

The URL may either be specified as a plain string, for convienence:

+
url = httpcore.URL("https://www.example.com/")
+
+ +

Or be constructed with explicitily pre-parsed components:

+
url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')
+
+ +

Using this second more explicit style allows integrations that are using +httpcore to pass through URLs that have already been parsed in order to use +libraries such as rfc-3986 rather than relying on the stdlib. It also ensures +that URL parsing is treated identically at both the networking level and at any +higher layers of abstraction.

+

The four components are important here, as they allow the URL to be precisely +specified in a pre-parsed format. They also allow certain types of request to +be created that could not otherwise be expressed.

+

For example, an HTTP request to http://www.example.com/ forwarded via a proxy +at http://localhost:8080...

+
# Constructs an HTTP request with a complete URL as the target:
+# GET https://www.example.com/ HTTP/1.1
+url = httpcore.URL(
+    scheme=b'http',
+    host=b'localhost',
+    port=8080,
+    target=b'https://www.example.com/'
+)
+request = httpcore.Request(
+    method="GET",
+    url=url
+)
+
+ +

Another example is constructing an OPTIONS * request...

+
# Constructs an 'OPTIONS *' HTTP request:
+# OPTIONS * HTTP/1.1
+url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')
+request = httpcore.Request(method="OPTIONS", url=url)
+
+ +

This kind of request is not possible to formulate with a URL string, +because the / delimiter is always used to demark the target from the +host/port portion of the URL.

+

For convenience, string-like arguments may be specified either as strings or +as bytes. However, once a request is being issue over-the-wire, the URL +components are always ultimately required to be a bytewise representation.

+

In order to avoid any ambiguity over character encodings, when strings are used +as arguments, they must be strictly limited to the ASCII range chr(0)-chr(127). +If you require a bytewise representation that is outside this range you must +handle the character encoding directly, and pass a bytes instance.

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

+__init__(self, url='', *, scheme=b'', host=b'', port=None, target=b'') + + + special + + +

+ +
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
urlUnion[bytes, str]

The complete URL as a string or bytes.

''
schemeUnion[bytes, str]

The URL scheme as a string or bytes. +Typically either "http" or "https".

b''
hostUnion[bytes, str]

The URL host as a string or bytes. Such as "www.example.com".

b''
portOptional[int]

The port to connect to. Either an integer or None.

None
targetUnion[bytes, str]

The target of the HTTP request. Such as "/items?search=red".

b''
+
+ +
+ + + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..afb45ae6 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"HTTPCore","text":"

Do one thing, and do it well.

The HTTP Core package provides a minimal low-level HTTP client, which does one thing only. Sending HTTP requests.

It does not provide any high level model abstractions over the API, does not handle redirects, multipart uploads, building authentication headers, transparent HTTP caching, URL parsing, session cookie handling, content or charset decoding, handling JSON, environment based configuration defaults, or any of that Jazz.

Some things HTTP Core does do:

  • Sending HTTP requests.
  • Thread-safe / task-safe connection pooling.
  • HTTP(S) proxy & SOCKS proxy support.
  • Supports HTTP/1.1 and HTTP/2.
  • Provides both sync and async interfaces.
  • Async backend support for asyncio and trio.
"},{"location":"#requirements","title":"Requirements","text":"

Python 3.8+

"},{"location":"#installation","title":"Installation","text":"

For HTTP/1.1 only support, install with:

$ pip install httpcore\n

For HTTP/1.1 and HTTP/2 support, install with:

$ pip install httpcore[http2]\n

For SOCKS proxy support, install with:

$ pip install httpcore[socks]\n
"},{"location":"#example","title":"Example","text":"

Let's check we're able to send HTTP requests:

import httpcore\n\nresponse = httpcore.request(\"GET\", \"https://www.example.com/\")\n\nprint(response)\n# <Response [200]>\nprint(response.status)\n# 200\nprint(response.headers)\n# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...]\nprint(response.content)\n# b'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>\\n\\n<meta charset=\"utf-8\"/>\\n ...'\n

Ready to get going?

Head over to the quickstart documentation.

"},{"location":"async/","title":"Async Support","text":"

HTTPX offers a standard synchronous API by default, but also gives you the option of an async client if you need it.

Async is a concurrency model that is far more efficient than multi-threading, and can provide significant performance benefits and enable the use of long-lived network connections such as WebSockets.

If you're working with an async web framework then you'll also want to use an async client for sending outgoing HTTP requests.

Launching concurrent async tasks is far more resource efficient than spawning multiple threads. The Python interpreter should be able to comfortably handle switching between over 1000 concurrent tasks, while a sensible number of threads in a thread pool might be to enable around 10 or 20 concurrent threads.

"},{"location":"async/#api-differences","title":"API differences","text":"

When using async support, you need make sure to use an async connection pool class:

# The async variation of `httpcore.ConnectionPool`\nasync with httpcore.AsyncConnectionPool() as http:\n    ...\n

Or if connecting via a proxy:

# The async variation of `httpcore.HTTPProxy`\nasync with httpcore.AsyncHTTPProxy() as proxy:\n    ...\n
"},{"location":"async/#sending-requests","title":"Sending requests","text":"

Sending requests with the async version of httpcore requires the await keyword:

import asyncio\nimport httpcore\n\nasync def main():\n    async with httpcore.AsyncConnectionPool() as http:\n        response = await http.request(\"GET\", \"https://www.example.com/\")\n\n\nasyncio.run(main())\n

When including content in the request, the content must either be bytes or an async iterable yielding bytes.

"},{"location":"async/#streaming-responses","title":"Streaming responses","text":"

Streaming responses also require a slightly different interface to the sync version:

  • with <pool>.stream(...) as response \u2192 async with <pool>.stream() as response.
  • for chunk in response.iter_stream() \u2192 async for chunk in response.aiter_stream().
  • response.read() \u2192 await response.aread().
  • response.close() \u2192 await response.aclose()

For example:

import asyncio\nimport httpcore\n\n\nasync def main():\n    async with httpcore.AsyncConnectionPool() as http:\n        async with http.stream(\"GET\", \"https://www.example.com/\") as response:\n            async for chunk in response.aiter_stream():\n                print(f\"Downloaded: {chunk}\")\n\n\nasyncio.run(main())\n
"},{"location":"async/#pool-lifespans","title":"Pool lifespans","text":"

When using httpcore in an async environment it is strongly recommended that you instantiate and use connection pools using the context managed style:

async with httpcore.AsyncConnectionPool() as http:\n    ...\n

To benefit from connection pooling it is recommended that you instantiate a single connection pool in this style, and pass it around throughout your application.

If you do want to use a connection pool without this style then you'll need to ensure that you explicitly close the pool once it is no longer required:

try:\n    http = httpcore.AsyncConnectionPool()\n    ...\nfinally:\n    await http.aclose()\n

This is a little different to the threaded context, where it's okay to simply instantiate a globally available connection pool, and then allow Python's garbage collection to deal with closing any connections in the pool, once the __del__ method is called.

The reason for this difference is that asynchronous code is not able to run within the context of the synchronous __del__ method, so there is no way for connections to be automatically closed at the point of garbage collection. This can lead to unterminated TCP connections still remaining after the Python interpreter quits.

"},{"location":"async/#supported-environments","title":"Supported environments","text":"

HTTPX supports either asyncio or trio as an async environment.

It will auto-detect which of those two to use as the backend for socket operations and concurrency primitives.

"},{"location":"async/#asyncio","title":"AsyncIO","text":"

AsyncIO is Python's built-in library for writing concurrent code with the async/await syntax.

Let's take a look at sending several outgoing HTTP requests concurrently, using asyncio:

import asyncio\nimport httpcore\nimport time\n\n\nasync def download(http, year):\n    await http.request(\"GET\", f\"https://en.wikipedia.org/wiki/{year}\")\n\n\nasync def main():\n    async with httpcore.AsyncConnectionPool() as http:\n        started = time.time()\n        # Here we use `asyncio.gather()` in order to run several tasks concurrently...\n        tasks = [download(http, year) for year in range(2000, 2020)]\n        await asyncio.gather(*tasks)\n        complete = time.time()\n\n        for connection in http.connections:\n            print(connection)\n        print(\"Complete in %.3f seconds\" % (complete - started))\n\n\nasyncio.run(main())\n
"},{"location":"async/#trio","title":"Trio","text":"

Trio is an alternative async library, designed around the the principles of structured concurrency.

import httpcore\nimport trio\nimport time\n\n\nasync def download(http, year):\n    await http.request(\"GET\", f\"https://en.wikipedia.org/wiki/{year}\")\n\n\nasync def main():\n    async with httpcore.AsyncConnectionPool() as http:\n        started = time.time()\n        async with trio.open_nursery() as nursery:\n            for year in range(2000, 2020):\n                nursery.start_soon(download, http, year)\n        complete = time.time()\n\n        for connection in http.connections:\n            print(connection)\n        print(\"Complete in %.3f seconds\" % (complete - started))\n\n\ntrio.run(main)\n
"},{"location":"async/#anyio","title":"AnyIO","text":"

AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio or trio. It blends in with native libraries of your chosen backend (defaults to asyncio).

The anyio library is designed around the the principles of structured concurrency, and brings many of the same correctness and usability benefits that Trio provides, while interoperating with existing asyncio libraries.

import httpcore\nimport anyio\nimport time\n\n\nasync def download(http, year):\n    await http.request(\"GET\", f\"https://en.wikipedia.org/wiki/{year}\")\n\n\nasync def main():\n    async with httpcore.AsyncConnectionPool() as http:\n        started = time.time()\n        async with anyio.create_task_group() as task_group:\n            for year in range(2000, 2020):\n                task_group.start_soon(download, http, year)\n        complete = time.time()\n\n        for connection in http.connections:\n            print(connection)\n        print(\"Complete in %.3f seconds\" % (complete - started))\n\n\nanyio.run(main)\n
"},{"location":"async/#reference","title":"Reference","text":""},{"location":"async/#httpcoreasyncconnectionpool","title":"httpcore.AsyncConnectionPool","text":"

A connection pool for making HTTP requests.

"},{"location":"async/#httpcore.AsyncConnectionPool.connections","title":"connections: List[httpcore.AsyncConnectionInterface] property readonly","text":"

Return a list of the connections currently in the pool.

For example:

>>> pool.connections\n[\n    <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,\n    <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,\n    <AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,\n]\n
"},{"location":"async/#httpcore.AsyncConnectionPool.__init__","title":"__init__(self, ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) special","text":"

A connection pool for making HTTP requests.

Parameters:

Name Type Description Default ssl_context Optional[ssl.SSLContext]

An SSL context to use for verifying connections. If not specified, the default httpcore.default_ssl_context() will be used.

None max_connections Optional[int]

The maximum number of concurrent HTTP connections that the pool should allow. Any attempt to send a request on a pool that would exceed this amount will block until a connection is available.

10 max_keepalive_connections Optional[int]

The maximum number of idle HTTP connections that will be maintained in the pool.

None keepalive_expiry Optional[float]

The duration in seconds that an idle HTTP connection may be maintained for before being expired from the pool.

None http1 bool

A boolean indicating if HTTP/1.1 requests should be supported by the connection pool. Defaults to True.

True http2 bool

A boolean indicating if HTTP/2 requests should be supported by the connection pool. Defaults to False.

False retries int

The maximum number of retries when trying to establish a connection.

0 local_address Optional[str]

Local address to connect from. Can also be used to connect using a particular address family. Using local_address=\"0.0.0.0\" will connect using an AF_INET address (IPv4), while using local_address=\"::\" will connect using an AF_INET6 address (IPv6).

None uds Optional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None network_backend Optional[httpcore.AsyncNetworkBackend]

A backend instance to use for handling network I/O.

None socket_options Optional[Iterable[Union[Tuple[int, int, int], Tuple[int, int, Union[bytes, bytearray]], Tuple[int, int, NoneType, int]]]]

Socket options that have to be included in the TCP socket when the connection was established.

None"},{"location":"async/#httpcore.AsyncConnectionPool.aclose","title":"aclose(self) async","text":"

Close any connections in the pool.

"},{"location":"async/#httpcore.AsyncConnectionPool.handle_async_request","title":"handle_async_request(self, request) async","text":"

Send an HTTP request, and return an HTTP response.

This is the core implementation that is called into by .request() or .stream().

"},{"location":"async/#httpcore.AsyncConnectionPool.response_closed","title":"response_closed(self, status) async","text":"

This method acts as a callback once the request/response cycle is complete.

It is called into from the ConnectionPoolByteStream.aclose() method.

"},{"location":"async/#httpcoreasynchttpproxy","title":"httpcore.AsyncHTTPProxy","text":"

A connection pool that sends requests via an HTTP proxy.

"},{"location":"async/#httpcore.AsyncHTTPProxy.__init__","title":"__init__(self, proxy_url, proxy_auth=None, proxy_headers=None, ssl_context=None, proxy_ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) special","text":"

A connection pool for making HTTP requests.

Parameters:

Name Type Description Default proxy_url Union[httpcore.URL, bytes, str]

The URL to use when connecting to the proxy server. For example \"http://127.0.0.1:8080/\".

required proxy_auth Optional[Tuple[Union[bytes, str], Union[bytes, str]]]

Any proxy authentication as a two-tuple of (username, password). May be either bytes or ascii-only str.

None proxy_headers Union[Mapping[Union[bytes, str], Union[bytes, str]], Sequence[Tuple[Union[bytes, str], Union[bytes, str]]]]

Any HTTP headers to use for the proxy requests. For example {\"Proxy-Authorization\": \"Basic <username>:<password>\"}.

None ssl_context Optional[ssl.SSLContext]

An SSL context to use for verifying connections. If not specified, the default httpcore.default_ssl_context() will be used.

None proxy_ssl_context Optional[ssl.SSLContext]

The same as ssl_context, but for a proxy server rather than a remote origin.

None max_connections Optional[int]

The maximum number of concurrent HTTP connections that the pool should allow. Any attempt to send a request on a pool that would exceed this amount will block until a connection is available.

10 max_keepalive_connections Optional[int]

The maximum number of idle HTTP connections that will be maintained in the pool.

None keepalive_expiry Optional[float]

The duration in seconds that an idle HTTP connection may be maintained for before being expired from the pool.

None http1 bool

A boolean indicating if HTTP/1.1 requests should be supported by the connection pool. Defaults to True.

True http2 bool

A boolean indicating if HTTP/2 requests should be supported by the connection pool. Defaults to False.

False retries int

The maximum number of retries when trying to establish a connection.

0 local_address Optional[str]

Local address to connect from. Can also be used to connect using a particular address family. Using local_address=\"0.0.0.0\" will connect using an AF_INET address (IPv4), while using local_address=\"::\" will connect using an AF_INET6 address (IPv6).

None uds Optional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None network_backend Optional[httpcore.AsyncNetworkBackend]

A backend instance to use for handling network I/O.

None"},{"location":"connection-pools/","title":"Connection Pools","text":"

While the top-level API provides convenience functions for working with httpcore, in practice you'll almost always want to take advantage of the connection pooling functionality that it provides.

To do so, instantiate a pool instance, and use it to send requests:

import httpcore\n\nhttp = httpcore.ConnectionPool()\nr = http.request(\"GET\", \"https://www.example.com/\")\n\nprint(r)\n# <Response [200]>\n

Connection pools support the same .request() and .stream() APIs as described in the Quickstart.

We can observe the benefits of connection pooling with a simple script like so:

import httpcore\nimport time\n\n\nhttp = httpcore.ConnectionPool()\nfor counter in range(5):\n    started = time.time()\n    response = http.request(\"GET\", \"https://www.example.com/\")\n    complete = time.time()\n    print(response, \"in %.3f seconds\" % (complete - started))\n

The output should demonstrate the initial request as being substantially slower than the subsequent requests:

<Response [200]> in {0.529} seconds\n<Response [200]> in {0.096} seconds\n<Response [200]> in {0.097} seconds\n<Response [200]> in {0.095} seconds\n<Response [200]> in {0.098} seconds\n

This is to be expected. Once we've established a connection to \"www.example.com\" we're able to reuse it for following requests.

"},{"location":"connection-pools/#configuration","title":"Configuration","text":"

The connection pool instance is also the main point of configuration. Let's take a look at the various options that it provides:

"},{"location":"connection-pools/#ssl-configuration","title":"SSL configuration","text":"
  • ssl_context: An SSL context to use for verifying connections. If not specified, the default httpcore.default_ssl_context() will be used.
"},{"location":"connection-pools/#pooling-configuration","title":"Pooling configuration","text":"
  • max_connections: The maximum number of concurrent HTTP connections that the pool should allow. Any attempt to send a request on a pool that would exceed this amount will block until a connection is available.
  • max_keepalive_connections: The maximum number of idle HTTP connections that will be maintained in the pool.
  • keepalive_expiry: The duration in seconds that an idle HTTP connection may be maintained for before being expired from the pool.
"},{"location":"connection-pools/#http-version-support","title":"HTTP version support","text":"
  • http1: A boolean indicating if HTTP/1.1 requests should be supported by the connection pool. Defaults to True.
  • http2: A boolean indicating if HTTP/2 requests should be supported by the connection pool. Defaults to False.
"},{"location":"connection-pools/#other-options","title":"Other options","text":"
  • retries: The maximum number of retries when trying to establish a connection.
  • local_address: Local address to connect from. Can also be used to connect using a particular address family. Using local_address=\"0.0.0.0\" will connect using an AF_INET address (IPv4), while using local_address=\"::\" will connect using an AF_INET6 address (IPv6).
  • uds: Path to a Unix Domain Socket to use instead of TCP sockets.
  • network_backend: A backend instance to use for handling network I/O.
  • socket_options: Socket options that have to be included in the TCP socket when the connection was established.
"},{"location":"connection-pools/#pool-lifespans","title":"Pool lifespans","text":"

Because connection pools hold onto network resources, careful developers may want to ensure that instances are properly closed once they are no longer required.

Working with a single global instance isn't a bad idea for many use case, since the connection pool will automatically be closed when the __del__ method is called on it:

# This is perfectly fine for most purposes.\n# The connection pool will automatically be closed when it is garbage collected,\n# or when the Python interpreter exits.\nhttp = httpcore.ConnectionPool()\n

However, to be more explicit around the resource usage, we can use the connection pool within a context manager:

with httpcore.ConnectionPool() as http:\n    ...\n

Or else close the pool explicitly:

http = httpcore.ConnectionPool()\ntry:\n    ...\nfinally:\n    http.close()\n
"},{"location":"connection-pools/#thread-and-task-safety","title":"Thread and task safety","text":"

Connection pools are designed to be thread-safe. Similarly, when using httpcore in an async context connection pools are task-safe.

This means that you can have a single connection pool instance shared by multiple threads.

"},{"location":"connection-pools/#reference","title":"Reference","text":""},{"location":"connection-pools/#httpcoreconnectionpool","title":"httpcore.ConnectionPool","text":"

A connection pool for making HTTP requests.

"},{"location":"connection-pools/#httpcore.ConnectionPool.connections","title":"connections: List[httpcore.ConnectionInterface] property readonly","text":"

Return a list of the connections currently in the pool.

For example:

>>> pool.connections\n[\n    <HTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,\n    <HTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,\n    <HTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,\n]\n
"},{"location":"connection-pools/#httpcore.ConnectionPool.__init__","title":"__init__(self, ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) special","text":"

A connection pool for making HTTP requests.

Parameters:

Name Type Description Default ssl_context Optional[ssl.SSLContext]

An SSL context to use for verifying connections. If not specified, the default httpcore.default_ssl_context() will be used.

None max_connections Optional[int]

The maximum number of concurrent HTTP connections that the pool should allow. Any attempt to send a request on a pool that would exceed this amount will block until a connection is available.

10 max_keepalive_connections Optional[int]

The maximum number of idle HTTP connections that will be maintained in the pool.

None keepalive_expiry Optional[float]

The duration in seconds that an idle HTTP connection may be maintained for before being expired from the pool.

None http1 bool

A boolean indicating if HTTP/1.1 requests should be supported by the connection pool. Defaults to True.

True http2 bool

A boolean indicating if HTTP/2 requests should be supported by the connection pool. Defaults to False.

False retries int

The maximum number of retries when trying to establish a connection.

0 local_address Optional[str]

Local address to connect from. Can also be used to connect using a particular address family. Using local_address=\"0.0.0.0\" will connect using an AF_INET address (IPv4), while using local_address=\"::\" will connect using an AF_INET6 address (IPv6).

None uds Optional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None network_backend Optional[httpcore.NetworkBackend]

A backend instance to use for handling network I/O.

None socket_options Optional[Iterable[Union[Tuple[int, int, int], Tuple[int, int, Union[bytes, bytearray]], Tuple[int, int, NoneType, int]]]]

Socket options that have to be included in the TCP socket when the connection was established.

None"},{"location":"connection-pools/#httpcore.ConnectionPool.close","title":"close(self)","text":"

Close any connections in the pool.

"},{"location":"connection-pools/#httpcore.ConnectionPool.handle_request","title":"handle_request(self, request)","text":"

Send an HTTP request, and return an HTTP response.

This is the core implementation that is called into by .request() or .stream().

"},{"location":"connection-pools/#httpcore.ConnectionPool.response_closed","title":"response_closed(self, status)","text":"

This method acts as a callback once the request/response cycle is complete.

It is called into from the ConnectionPoolByteStream.close() method.

"},{"location":"connections/","title":"Connections","text":"

TODO

"},{"location":"connections/#reference","title":"Reference","text":""},{"location":"connections/#httpcorehttpconnection","title":"httpcore.HTTPConnection","text":""},{"location":"connections/#httpcore.HTTPConnection.has_expired","title":"has_expired(self)","text":"

Return True if the connection is in a state where it should be closed.

This either means that the connection is idle and it has passed the expiry time on its keep-alive, or that server has sent an EOF.

"},{"location":"connections/#httpcore.HTTPConnection.is_available","title":"is_available(self)","text":"

Return True if the connection is currently able to accept an outgoing request.

An HTTP/1.1 connection will only be available if it is currently idle.

An HTTP/2 connection will be available so long as the stream ID space is not yet exhausted, and the connection is not in an error state.

While the connection is being established we may not yet know if it is going to result in an HTTP/1.1 or HTTP/2 connection. The connection should be treated as being available, but might ultimately raise NewConnectionRequired required exceptions if multiple requests are attempted over a connection that ends up being established as HTTP/1.1.

"},{"location":"connections/#httpcore.HTTPConnection.is_closed","title":"is_closed(self)","text":"

Return True if the connection has been closed.

Used when a response is closed to determine if the connection may be returned to the connection pool or not.

"},{"location":"connections/#httpcore.HTTPConnection.is_idle","title":"is_idle(self)","text":"

Return True if the connection is currently idle.

"},{"location":"connections/#httpcorehttp11connection","title":"httpcore.HTTP11Connection","text":""},{"location":"connections/#httpcore.HTTP11Connection.has_expired","title":"has_expired(self)","text":"

Return True if the connection is in a state where it should be closed.

This either means that the connection is idle and it has passed the expiry time on its keep-alive, or that server has sent an EOF.

"},{"location":"connections/#httpcore.HTTP11Connection.is_available","title":"is_available(self)","text":"

Return True if the connection is currently able to accept an outgoing request.

An HTTP/1.1 connection will only be available if it is currently idle.

An HTTP/2 connection will be available so long as the stream ID space is not yet exhausted, and the connection is not in an error state.

While the connection is being established we may not yet know if it is going to result in an HTTP/1.1 or HTTP/2 connection. The connection should be treated as being available, but might ultimately raise NewConnectionRequired required exceptions if multiple requests are attempted over a connection that ends up being established as HTTP/1.1.

"},{"location":"connections/#httpcore.HTTP11Connection.is_closed","title":"is_closed(self)","text":"

Return True if the connection has been closed.

Used when a response is closed to determine if the connection may be returned to the connection pool or not.

"},{"location":"connections/#httpcore.HTTP11Connection.is_idle","title":"is_idle(self)","text":"

Return True if the connection is currently idle.

"},{"location":"connections/#httpcorehttp2connection","title":"httpcore.HTTP2Connection","text":""},{"location":"connections/#httpcore.HTTP2Connection.has_expired","title":"has_expired(self)","text":"

Return True if the connection is in a state where it should be closed.

This either means that the connection is idle and it has passed the expiry time on its keep-alive, or that server has sent an EOF.

"},{"location":"connections/#httpcore.HTTP2Connection.is_available","title":"is_available(self)","text":"

Return True if the connection is currently able to accept an outgoing request.

An HTTP/1.1 connection will only be available if it is currently idle.

An HTTP/2 connection will be available so long as the stream ID space is not yet exhausted, and the connection is not in an error state.

While the connection is being established we may not yet know if it is going to result in an HTTP/1.1 or HTTP/2 connection. The connection should be treated as being available, but might ultimately raise NewConnectionRequired required exceptions if multiple requests are attempted over a connection that ends up being established as HTTP/1.1.

"},{"location":"connections/#httpcore.HTTP2Connection.is_closed","title":"is_closed(self)","text":"

Return True if the connection has been closed.

Used when a response is closed to determine if the connection may be returned to the connection pool or not.

"},{"location":"connections/#httpcore.HTTP2Connection.is_idle","title":"is_idle(self)","text":"

Return True if the connection is currently idle.

"},{"location":"exceptions/","title":"Exceptions","text":"

The following exceptions may be raised when sending a request:

  • httpcore.TimeoutException
    • httpcore.PoolTimeout
    • httpcore.ConnectTimeout
    • httpcore.ReadTimeout
    • httpcore.WriteTimeout
  • httpcore.NetworkError
    • httpcore.ConnectError
    • httpcore.ReadError
    • httpcore.WriteError
  • httpcore.ProtocolError
    • httpcore.RemoteProtocolError
    • httpcore.LocalProtocolError
  • httpcore.ProxyError
  • httpcore.UnsupportedProtocol
"},{"location":"extensions/","title":"Extensions","text":"

The request/response API used by httpcore is kept deliberately simple and explicit.

The Request and Response models are pretty slim wrappers around this core API:

# Pseudo-code expressing the essentials of the request/response model.\n(\nstatus_code: int,\nheaders: List[Tuple(bytes, bytes)],\nstream: Iterable[bytes]\n) = handle_request(\nmethod: bytes,\nurl: URL,\nheaders: List[Tuple(bytes, bytes)],\nstream: Iterable[bytes]\n)\n

This is everything that's needed in order to represent an HTTP exchange.

Well... almost.

There is a maxim in Computer Science that \"All non-trivial abstractions, to some degree, are leaky\". When an expression is leaky, it's important that it ought to at least leak only in well-defined places.

In order to handle cases that don't otherwise fit inside this core abstraction, httpcore requests and responses have 'extensions'. These are a dictionary of optional additional information.

Let's expand on our request/response abstraction...

# Pseudo-code expressing the essentials of the request/response model,\n# plus extensions allowing for additional API that does not fit into\n# this abstraction.\n(\nstatus_code: int,\nheaders: List[Tuple(bytes, bytes)],\nstream: Iterable[bytes],\nextensions: dict\n) = handle_request(\nmethod: bytes,\nurl: URL,\nheaders: List[Tuple(bytes, bytes)],\nstream: Iterable[bytes],\nextensions: dict\n)\n

Several extensions are supported both on the request:

r = httpcore.request(\n    \"GET\",\n    \"https://www.example.com\",\n    extensions={\"timeout\": {\"connect\": 5.0}}\n)\n

And on the response:

r = httpcore.request(\"GET\", \"https://www.example.com\")\n\nprint(r.extensions[\"http_version\"])\n# When using HTTP/1.1 on the client side, the server HTTP response\n# could feasibly be one of b\"HTTP/0.9\", b\"HTTP/1.0\", or b\"HTTP/1.1\".\n
"},{"location":"extensions/#request-extensions","title":"Request Extensions","text":""},{"location":"extensions/#timeout","title":"\"timeout\"","text":"

A dictionary of str: Optional[float] timeout values.

May include values for 'connect', 'read', 'write', or 'pool'.

For example:

# Timeout if a connection takes more than 5 seconds to established, or if\n# we are blocked waiting on the connection pool for more than 10 seconds.\nr = httpcore.request(\n    \"GET\",\n    \"https://www.example.com\",\n    extensions={\"timeout\": {\"connect\": 5.0, \"pool\": 10.0}}\n)\n
"},{"location":"extensions/#trace","title":"\"trace\"","text":"

The trace extension allows a callback handler to be installed to monitor the internal flow of events within httpcore. The simplest way to explain this is with an example:

import httpcore\n\ndef log(event_name, info):\n    print(event_name, info)\n\nr = httpcore.request(\"GET\", \"https://www.example.com/\", extensions={\"trace\": log})\n# connection.connect_tcp.started {'host': 'www.example.com', 'port': 443, 'local_address': None, 'timeout': None}\n# connection.connect_tcp.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f94d0>}\n# connection.start_tls.started {'ssl_context': <ssl.SSLContext object at 0x1093ee750>, 'server_hostname': b'www.example.com', 'timeout': None}\n# connection.start_tls.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f9450>}\n# http11.send_request_headers.started {'request': <Request [b'GET']>}\n# http11.send_request_headers.complete {'return_value': None}\n# http11.send_request_body.started {'request': <Request [b'GET']>}\n# http11.send_request_body.complete {'return_value': None}\n# http11.receive_response_headers.started {'request': <Request [b'GET']>}\n# http11.receive_response_headers.complete {'return_value': (b'HTTP/1.1', 200, b'OK', [(b'Age', b'553715'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Thu, 21 Oct 2021 17:08:42 GMT'), (b'Etag', b'\"3147526947+ident\"'), (b'Expires', b'Thu, 28 Oct 2021 17:08:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1DCD)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])}\n# http11.receive_response_body.started {'request': <Request [b'GET']>}\n# http11.receive_response_body.complete {'return_value': None}\n# http11.response_closed.started {}\n# http11.response_closed.complete {'return_value': None}\n

The event_name and info arguments here will be one of the following:

  • {event_type}.{event_name}.started, <dictionary of keyword arguments>
  • {event_type}.{event_name}.complete, {\"return_value\": <...>}
  • {event_type}.{event_name}.failed, {\"exception\": <...>}

Note that when using the async variant of httpcore the handler function passed to \"trace\" must be an async def ... function.

The following event types are currently exposed...

Establishing the connection

  • \"connection.connect_tcp\"
  • \"connection.connect_unix_socket\"
  • \"connection.start_tls\"

HTTP/1.1 events

  • \"http11.send_request_headers\"
  • \"http11.send_request_body\"
  • \"http11.receive_response\"
  • \"http11.receive_response_body\"
  • \"http11.response_closed\"

HTTP/2 events

  • \"http2.send_connection_init\"
  • \"http2.send_request_headers\"
  • \"http2.send_request_body\"
  • \"http2.receive_response_headers\"
  • \"http2.receive_response_body\"
  • \"http2.response_closed\"
"},{"location":"extensions/#sni_hostname","title":"\"sni_hostname\"","text":"

The server's hostname, which is used to confirm the hostname supplied by the SSL certificate.

For example:

headers = {\"Host\": \"www.encode.io\"}\nextensions = {\"sni_hostname\": \"www.encode.io\"}\nresponse = httpcore.request(\n    \"GET\",\n    \"https://185.199.108.153\",\n    headers=headers,\n    extensions=extensions\n)\n
"},{"location":"extensions/#response-extensions","title":"Response Extensions","text":""},{"location":"extensions/#http_version","title":"\"http_version\"","text":"

The HTTP version, as bytes. Eg. b\"HTTP/1.1\".

When using HTTP/1.1 the response line includes an explicit version, and the value of this key could feasibly be one of b\"HTTP/0.9\", b\"HTTP/1.0\", or b\"HTTP/1.1\".

When using HTTP/2 there is no further response versioning included in the protocol, and the value of this key will always be b\"HTTP/2\".

"},{"location":"extensions/#reason_phrase","title":"\"reason_phrase\"","text":"

The reason-phrase of the HTTP response, as bytes. For example b\"OK\". Some servers may include a custom reason phrase, although this is not recommended.

HTTP/2 onwards does not include a reason phrase on the wire.

When no key is included, a default based on the status code may be used.

"},{"location":"extensions/#stream_id","title":"\"stream_id\"","text":"

When HTTP/2 is being used the \"stream_id\" response extension can be accessed to determine the ID of the data stream that the response was sent on.

"},{"location":"extensions/#network_stream","title":"\"network_stream\"","text":"

The \"network_stream\" extension allows developers to handle HTTP CONNECT and Upgrade requests, by providing an API that steps outside the standard request/response model, and can directly read or write to the network.

The interface provided by the network stream:

  • read(max_bytes, timeout = None) -> bytes
  • write(buffer, timeout = None)
  • close()
  • start_tls(ssl_context, server_hostname = None, timeout = None) -> NetworkStream
  • get_extra_info(info) -> Any

This API can be used as the foundation for working with HTTP proxies, WebSocket upgrades, and other advanced use-cases.

See the network backends documentation for more information on working directly with network streams.

"},{"location":"extensions/#connect-requests","title":"CONNECT requests","text":"

A proxy CONNECT request using the network stream:

# Formulate a CONNECT request...\n#\n# This will establish a connection to 127.0.0.1:8080, and then send the following...\n#\n# CONNECT http://www.example.com HTTP/1.1\n# Host: 127.0.0.1:8080\nurl = httpcore.URL(b\"http\", b\"127.0.0.1\", 8080, b\"http://www.example.com\")\nwith httpcore.stream(\"CONNECT\", url) as response:\n    network_stream = response.extensions[\"network_stream\"]\n\n    # Upgrade to an SSL stream...\n    network_stream = network_stream.start_tls(\n        ssl_context=httpcore.default_ssl_context(),\n        hostname=b\"www.example.com\",\n    )\n\n    # Manually send an HTTP request over the network stream, and read the response...\n    #\n    # For a more complete example see the httpcore `TunnelHTTPConnection` implementation.\n    network_stream.write(b\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n    data = network_stream.read()\n    print(data)\n
"},{"location":"extensions/#upgrade-requests","title":"Upgrade requests","text":"

Using the wsproto package to handle a websockets session:

import httpcore\nimport wsproto\nimport os\nimport base64\n\n\nurl = \"http://127.0.0.1:8000/\"\nheaders = {\n    b\"Connection\": b\"Upgrade\",\n    b\"Upgrade\": b\"WebSocket\",\n    b\"Sec-WebSocket-Key\": base64.b64encode(os.urandom(16)),\n    b\"Sec-WebSocket-Version\": b\"13\"\n}\nwith httpcore.stream(\"GET\", url, headers=headers) as response:\n    if response.status != 101:\n        raise Exception(\"Failed to upgrade to websockets\", response)\n\n    # Get the raw network stream.\n    network_steam = response.extensions[\"network_stream\"]\n\n    # Write a WebSocket text frame to the stream.\n    ws_connection = wsproto.Connection(wsproto.ConnectionType.CLIENT)\n    message = wsproto.events.TextMessage(\"hello, world!\")\n    outgoing_data = ws_connection.send(message)\n    network_steam.write(outgoing_data)\n\n    # Wait for a response.\n    incoming_data = network_steam.read(max_bytes=4096)\n    ws_connection.receive_data(incoming_data)\n    for event in ws_connection.events():\n        if isinstance(event, wsproto.events.TextMessage):\n            print(\"Got data:\", event.data)\n\n    # Write a WebSocket close to the stream.\n    message = wsproto.events.CloseConnection(code=1000)\n    outgoing_data = ws_connection.send(message)\n    network_steam.write(outgoing_data)\n
"},{"location":"extensions/#extra-network-information","title":"Extra network information","text":"

The network stream abstraction also allows access to various low-level information that may be exposed by the underlying socket:

response = httpcore.request(\"GET\", \"https://www.example.com\")\nnetwork_stream = response.extensions[\"network_stream\"]\n\nclient_addr = network_stream.get_extra_info(\"client_addr\")\nserver_addr = network_stream.get_extra_info(\"server_addr\")\nprint(\"Client address\", client_addr)\nprint(\"Server address\", server_addr)\n

The socket SSL information is also available through this interface, although you need to ensure that the underlying connection is still open, in order to access it...

with httpcore.stream(\"GET\", \"https://www.example.com\") as response:\n    network_stream = response.extensions[\"network_stream\"]\n\n    ssl_object = network_stream.get_extra_info(\"ssl_object\")\n    print(\"TLS version\", ssl_object.version())\n
"},{"location":"http2/","title":"HTTP/2","text":"

HTTP/2 is a major new iteration of the HTTP protocol, that provides a more efficient transport, with potential performance benefits. HTTP/2 does not change the core semantics of the request or response, but alters the way that data is sent to and from the server.

Rather than the text format that HTTP/1.1 uses, HTTP/2 is a binary format. The binary format provides full request and response multiplexing, and efficient compression of HTTP headers. The stream multiplexing means that where HTTP/1.1 requires one TCP stream for each concurrent request, HTTP/2 allows a single TCP stream to handle multiple concurrent requests.

HTTP/2 also provides support for functionality such as response prioritization, and server push.

For a comprehensive guide to HTTP/2 you may want to check out \"HTTP2 Explained\".

"},{"location":"http2/#enabling-http2","title":"Enabling HTTP/2","text":"

When using the httpcore client, HTTP/2 support is not enabled by default, because HTTP/1.1 is a mature, battle-hardened transport layer, and our HTTP/1.1 implementation may be considered the more robust option at this point in time. It is possible that a future version of httpcore may enable HTTP/2 support by default.

If you're issuing highly concurrent requests you might want to consider trying out our HTTP/2 support. You can do so by first making sure to install the optional HTTP/2 dependencies...

$ pip install httpcore[http2]\n

And then instantiating a connection pool with HTTP/2 support enabled:

import httpcore\n\npool = httpcore.ConnectionPool(http2=True)\n

We can take a look at the difference in behaviour by issuing several outgoing requests in parallel.

Start out by using a standard HTTP/1.1 connection pool:

import httpcore\nimport concurrent.futures\nimport time\n\n\ndef download(http, year):\n    http.request(\"GET\", f\"https://en.wikipedia.org/wiki/{year}\")\n\n\ndef main():\n    with httpcore.ConnectionPool() as http:\n        started = time.time()\n        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as threads:\n            for year in range(2000, 2020):\n                threads.submit(download, http, year)\n        complete = time.time()\n\n        for connection in http.connections:\n            print(connection)\n        print(\"Complete in %.3f seconds\" % (complete - started))\n\n\nmain()\n

If you run this with an HTTP/1.1 connection pool, you ought to see output similar to the following:

<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 2]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 3]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 6]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 5]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>,\n<HTTPConnection ['https://en.wikipedia.org:443', HTTP/1.1, IDLE, Request Count: 1]>\nComplete in 0.586 seconds\n

We can see that the connection pool required a number of connections in order to handle the parallel requests.

If we now upgrade our connection pool to support HTTP/2:

with httpcore.ConnectionPool(http2=True) as http:\n    ...\n

And run the same script again, we should end up with something like this:

<HTTPConnection ['https://en.wikipedia.org:443', HTTP/2, IDLE, Request Count: 20]>\nComplete in 0.573 seconds\n

All of our requests have been handled over a single connection.

Switching to HTTP/2 should not necessarily be considered an \"upgrade\". It is more complex, and requires more computational power, and so particularly in an interpreted language like Python it could be slower in some instances. Moreover, utilising multiple connections may end up connecting to multiple hosts, and could sometimes appear faster to the client, at the cost of requiring more server resources. Enabling HTTP/2 is most likely to be beneficial if you are sending requests in high concurrency, and may often be more well suited to an async context, rather than multi-threading.

"},{"location":"http2/#inspecting-the-http-version","title":"Inspecting the HTTP version","text":"

Enabling HTTP/2 support on the client does not necessarily mean that your requests and responses will be transported over HTTP/2, since both the client and the server need to support HTTP/2. If you connect to a server that only supports HTTP/1.1 the client will use a standard HTTP/1.1 connection instead.

You can determine which version of the HTTP protocol was used by examining the \"http_version\" response extension.

import httpcore\n\npool = httpcore.ConnectionPool(http2=True)\nresponse = pool.request(\"GET\", \"https://www.example.com/\")\n\n# Should be one of b\"HTTP/2\", b\"HTTP/1.1\", b\"HTTP/1.0\", or b\"HTTP/0.9\".\nprint(response.extensions[\"http_version\"])\n

See the extensions documentation for more details.

"},{"location":"http2/#http2-negotiation","title":"HTTP/2 negotiation","text":"

Robust servers need to support both HTTP/2 and HTTP/1.1 capable clients, and so need some way to \"negotiate\" with the client which protocol version will be used.

"},{"location":"http2/#http2-over-https","title":"HTTP/2 over HTTPS","text":"

Generally the method used is for the server to advertise if it has HTTP/2 support during the part of the SSL connection handshake. This is known as ALPN - \"Application Layer Protocol Negotiation\".

Most browsers only provide HTTP/2 support over HTTPS connections, and this is also the default behaviour that httpcore provides. If you enable HTTP/2 support you should still expect to see HTTP/1.1 connections for any http:// URLs.

"},{"location":"http2/#http2-over-http","title":"HTTP/2 over HTTP","text":"

Servers can optionally also support HTTP/2 over HTTP by supporting the Upgrade: h2c header.

This mechanism is not supported by httpcore. It requires an additional round-trip between the client and server, and also requires any request body to be sent twice.

"},{"location":"http2/#prior-knowledge","title":"Prior Knowledge","text":"

If you know in advance that the server you are communicating with will support HTTP/2, then you can enforce that the client uses HTTP/2, without requiring either ALPN support or an HTTP Upgrade: h2c header.

This is managed by disabling HTTP/1.1 support on the connection pool:

pool = httpcore.ConnectionPool(http1=False, http2=True)\n
"},{"location":"http2/#request-response-headers","title":"Request & response headers","text":"

Because HTTP/2 frames the requests and responses somewhat differently to HTTP/1.1, there is a difference in some of the headers that are used.

In order for the httpcore library to support both HTTP/1.1 and HTTP/2 transparently, the HTTP/1.1 style is always used throughout the API. Any differences in header styles are only mapped onto HTTP/2 at the internal network layer.

"},{"location":"http2/#request-headers","title":"Request headers","text":"

The following pseudo-headers are used by HTTP/2 in the request:

  • :method - The request method.
  • :path - Taken from the URL of the request.
  • :authority - Equivalent to the Host header in HTTP/1.1. In httpcore this is represented using the request Host header, which is automatically populated from the request URL if no Host header is explicitly included.
  • :scheme - Taken from the URL of the request.

These pseudo-headers are included in httpcore as part of the request.method and request.url attributes, and through the request.headers[\"Host\"] header. They are not exposed directly by their psuedo-header names.

The one other difference to be aware of is the Transfer-Encoding: chunked header.

In HTTP/2 this header is never used, since streaming data is framed using a different mechanism.

In httpcore the Transfer-Encoding: chunked header is always used to represent the presence of a streaming body on the request, and is automatically populated if required. However the header is only sent if the underlying connection ends up being HTTP/1.1, and is omitted if the underlying connection ends up being HTTP/2.

"},{"location":"http2/#response-headers","title":"Response headers","text":"

The following pseudo-header is used by HTTP/2 in the response:

  • :status - The response status code.

In httpcore this is represented by the response.status attribute, rather than being exposed as a psuedo-header.

"},{"location":"logging/","title":"Logging","text":"

If you need to inspect the internal behaviour of httpcore, you can use Python's standard logging to output debug level information.

For example, the following configuration...

import logging\nimport httpcore\n\nlogging.basicConfig(\n    format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n    datefmt=\"%Y-%m-%d %H:%M:%S\",\n    level=logging.DEBUG\n)\n\nhttpcore.request('GET', 'https://www.example.com')\n

Will send debug level output to the console, or wherever stdout is directed too...

DEBUG [2023-01-09 14:44:00] httpcore.connection - connect_tcp.started host='www.example.com' port=443 local_address=None timeout=None\nDEBUG [2023-01-09 14:44:00] httpcore.connection - connect_tcp.complete return_value=<httpcore.backends.sync.SyncStream object at 0x109ba6610>\nDEBUG [2023-01-09 14:44:00] httpcore.connection - start_tls.started ssl_context=<ssl.SSLContext object at 0x109e427b0> server_hostname='www.example.com' timeout=None\nDEBUG [2023-01-09 14:44:00] httpcore.connection - start_tls.complete return_value=<httpcore.backends.sync.SyncStream object at 0x109e8b050>\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_headers.started request=<Request [b'GET']>\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_headers.complete\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_body.started request=<Request [b'GET']>\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - send_request_body.complete\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_headers.started request=<Request [b'GET']>\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Age', b'572646'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Mon, 09 Jan 2023 14:44:00 GMT'), (b'Etag', b'\"3147526947+ident\"'), (b'Expires', b'Mon, 16 Jan 2023 14:44:00 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1D18)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_body.started request=<Request [b'GET']>\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - receive_response_body.complete\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - response_closed.started\nDEBUG [2023-01-09 14:44:00] httpcore.http11 - response_closed.complete\nDEBUG [2023-01-09 14:44:00] httpcore.connection - close.started\nDEBUG [2023-01-09 14:44:00] httpcore.connection - close.complete\n

The exact formatting of the debug logging may be subject to change across different versions of httpcore. If you need to rely on a particular format it is recommended that you pin installation of the package to a fixed version.

"},{"location":"network-backends/","title":"Network Backends","text":"

The API layer at which httpcore interacts with the network is described as the network backend. Various backend implementations are provided, allowing httpcore to handle networking in different runtime contexts.

"},{"location":"network-backends/#working-with-network-backends","title":"Working with network backends","text":""},{"location":"network-backends/#the-default-network-backend","title":"The default network backend","text":"

Typically you won't need to specify a network backend, as a default will automatically be selected. However, understanding how the network backends fit in may be useful if you want to better understand the underlying architecture. Let's start by seeing how we can explicitly select the network backend.

First we're making a standard HTTP request, using a connection pool:

import httpcore\n\nwith httpcore.ConnectionPool() as http:\n    response = http.request('GET', 'https://www.example.com')\n    print(response)\n

We can also have the same behavior, but be explicit with our selection of the network backend:

import httpcore\n\nnetwork_backend = httpcore.SyncBackend()\nwith httpcore.ConnectionPool(network_backend=network_backend) as http:\n    response = http.request('GET', 'https://www.example.com')\n    print(response)\n

The httpcore.SyncBackend() implementation handles the opening of TCP connections, and operations on the socket stream, such as reading, writing, and closing the connection.

We can get a better understanding of this by using a network backend to send a basic HTTP/1.1 request directly:

import httpcore\n\n# Create an SSL context using 'certifi' for the certificates.\nssl_context = httpcore.default_ssl_context()\n\n# A basic HTTP/1.1 request as a plain bytestring.\nrequest = b'\\r\\n'.join([\n    b'GET / HTTP/1.1',\n    b'Host: www.example.com',\n    b'Accept: */*',\n    b'Connection: close',\n    b''\n])\n\n# Open a TCP stream and upgrade it to SSL.\nnetwork_backend = httpcore.SyncBackend()\nnetwork_stream = network_backend.connect_tcp(\"www.example.com\", 443)\nnetwork_stream = network_stream.start_tls(ssl_context, server_hostname=\"www.example.com\")\n\n# Send the HTTP request.\nnetwork_stream.write(request)\n\n# Read the HTTP response.\nwhile True:\n    response = network_stream.read(max_bytes=4096)\n    if response == b'':\n        break\n    print(response)\n\n# The output should look something like this:\n#\n# b'HTTP/1.1 200 OK\\r\\nAge: 600005\\r\\n [...] Content-Length: 1256\\r\\nConnection: close\\r\\n\\r\\n'\n# b'<!doctype html>\\n<html>\\n<head>\\n    <title>Example Domain</title> [...] </html>\\n'\n
"},{"location":"network-backends/#async-network-backends","title":"Async network backends","text":"

If we're working with an async codebase, then we need to select a different backend.

The httpcore.AnyIOBackend is suitable for usage if you're running under asyncio. This is a networking backend implemented using the anyio package.

import httpcore\nimport asyncio\n\nasync def main():\n    network_backend = httpcore.AnyIOBackend()\n    async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:\n        response = await http.request('GET', 'https://www.example.com')\n        print(response)\n\nasyncio.run(main())\n

The AnyIOBackend will work when running under either asyncio or trio. However, if you're working with async using the trio framework, then we recommend using the httpcore.TrioBackend.

This will give you the same kind of networking behavior you'd have using AnyIOBackend, but there will be a little less indirection so it will be marginally more efficient and will present cleaner tracebacks in error cases.

import httpcore\nimport trio\n\nasync def main():\n    network_backend = httpcore.TrioBackend()\n    async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:\n        response = await http.request('GET', 'https://www.example.com')\n        print(response)\n\ntrio.run(main)\n
"},{"location":"network-backends/#mock-network-backends","title":"Mock network backends","text":"

There are also mock network backends available that can be useful for testing purposes. These backends accept a list of bytes, and return network stream interfaces that return those byte streams.

Here's an example of mocking a simple HTTP/1.1 response...

import httpcore\n\nnetwork_backend = httpcore.MockBackend([\n    b\"HTTP/1.1 200 OK\\r\\n\",\n    b\"Content-Type: plain/text\\r\\n\",\n    b\"Content-Length: 13\\r\\n\",\n    b\"\\r\\n\",\n    b\"Hello, world!\",\n])\nwith httpcore.ConnectionPool(network_backend=network_backend) as http:\n    response = http.request(\"GET\", \"https://example.com/\")\n    print(response.extensions['http_version'])\n    print(response.status)\n    print(response.content)\n

Mocking a HTTP/2 response is more complex, since it uses a binary format...

import hpack\nimport hyperframe.frame\nimport httpcore\n\ncontent = [\n    hyperframe.frame.SettingsFrame().serialize(),\n    hyperframe.frame.HeadersFrame(\n        stream_id=1,\n        data=hpack.Encoder().encode(\n            [\n                (b\":status\", b\"200\"),\n                (b\"content-type\", b\"plain/text\"),\n            ]\n        ),\n        flags=[\"END_HEADERS\"],\n    ).serialize(),\n    hyperframe.frame.DataFrame(\n        stream_id=1, data=b\"Hello, world!\", flags=[\"END_STREAM\"]\n    ).serialize(),\n]\n# Note that we instantiate the mock backend with an `http2=True` argument.\n# This ensures that the mock network stream acts as if the `h2` ALPN flag has been set,\n# and causes the connection pool to interact with the connection using HTTP/2.\nnetwork_backend = httpcore.MockBackend(content, http2=True)\nwith httpcore.ConnectionPool(network_backend=network_backend) as http:\n    response = http.request(\"GET\", \"https://example.com/\")\n    print(response.extensions['http_version'])\n    print(response.status)\n    print(response.content)\n
"},{"location":"network-backends/#custom-network-backends","title":"Custom network backends","text":"

The base interface for network backends is provided as public API, allowing you to implement custom networking behavior.

You can use this to provide advanced networking functionality such as:

  • Network recording / replay.
  • In-depth debug tooling.
  • Handling non-standard SSL or DNS requirements.

Here's an example that records the network response to a file on disk:

import httpcore\n\n\nclass RecordingNetworkStream(httpcore.NetworkStream):\n    def __init__(self, record_file, stream):\n        self.record_file = record_file\n        self.stream = stream\n\n    def read(self, max_bytes, timeout=None):\n        data = self.stream.read(max_bytes, timeout=timeout)\n        self.record_file.write(data)\n        return data\n\n    def write(self, buffer, timeout=None):\n        self.stream.write(buffer, timeout=timeout)\n\n    def close(self) -> None:\n        self.stream.close()\n\n    def start_tls(\n        self,\n        ssl_context,\n        server_hostname=None,\n        timeout=None,\n    ):\n        self.stream = self.stream.start_tls(\n            ssl_context, server_hostname=server_hostname, timeout=timeout\n        )\n        return self\n\n    def get_extra_info(self, info):\n        return self.stream.get_extra_info(info)\n\n\nclass RecordingNetworkBackend(httpcore.NetworkBackend):\n\"\"\"\n    A custom network backend that records network responses.\n    \"\"\"\n    def __init__(self, record_file):\n        self.record_file = record_file\n        self.backend = httpcore.SyncBackend()\n\n    def connect_tcp(\n        self,\n        host,\n        port,\n        timeout=None,\n        local_address=None,\n        socket_options=None,\n    ):\n        # Note that we're only using a single record file here,\n        # so even if multiple connections are opened the network\n        # traffic will all write to the same file.\n\n        # An alternative implementation might automatically use\n        # a new file for each opened connection.\n        stream = self.backend.connect_tcp(\n            host,\n            port,\n            timeout=timeout,\n            local_address=local_address,\n            socket_options=socket_options\n        )\n        return RecordingNetworkStream(self.record_file, stream)\n\n\n# Once you make the request, the raw HTTP/1.1 response will be available\n#\u00a0in the 'network-recording' file.\n#\n# Try switching to `http2=True` to see the difference when recording HTTP/2 binary network traffic,\n# or add `headers={'Accept-Encoding': 'gzip'}` to see HTTP content compression.\nwith open(\"network-recording\", \"wb\") as record_file:\n    network_backend = RecordingNetworkBackend(record_file)\n    with httpcore.ConnectionPool(network_backend=network_backend) as http:\n        response = http.request(\"GET\", \"https://www.example.com/\")\n        print(response)\n
"},{"location":"network-backends/#reference","title":"Reference","text":""},{"location":"network-backends/#networking-backends","title":"Networking Backends","text":"
  • httpcore.SyncBackend
  • httpcore.AnyIOBackend
  • httpcore.TrioBackend
"},{"location":"network-backends/#mock-backends","title":"Mock Backends","text":"
  • httpcore.MockBackend
  • httpcore.MockStream
  • httpcore.AsyncMockBackend
  • httpcore.AsyncMockStream
"},{"location":"network-backends/#base-interface","title":"Base Interface","text":"
  • httpcore.NetworkBackend
  • httpcore.NetworkStream
  • httpcore.AsyncNetworkBackend
  • httpcore.AsyncNetworkStream
"},{"location":"proxies/","title":"Proxies","text":"

The httpcore package provides support for HTTP proxies, using either \"HTTP Forwarding\" or \"HTTP Tunnelling\". Forwarding is a proxy mechanism for sending requests to http URLs via an intermediate proxy. Tunnelling is a proxy mechanism for sending requests to https URLs via an intermediate proxy.

Sending requests via a proxy is very similar to sending requests using a standard connection pool:

import httpcore\n\nproxy = httpcore.HTTPProxy(proxy_url=\"http://127.0.0.1:8080/\")\nr = proxy.request(\"GET\", \"https://www.example.com/\")\n\nprint(r)\n# <Response [200]>\n

You can test the httpcore proxy support, using the Python proxy.py tool:

$ pip install proxy.py\n$ proxy --hostname 127.0.0.1 --port 8080\n

Requests will automatically use either forwarding or tunnelling, depending on if the scheme is http or https.

"},{"location":"proxies/#authentication","title":"Authentication","text":"

Proxy authentication can be included in the initial configuration:

import httpcore\n\n# A `Proxy-Authorization` header will be included on the initial proxy connection.\nproxy = httpcore.HTTPProxy(\n    proxy_url=\"http://127.0.0.1:8080/\",\n    proxy_auth=(\"<username>\", \"<password>\")\n)\n

Custom headers can also be included:

import httpcore\nimport base64\n\n# Construct and include a `Proxy-Authorization` header.\nauth = base64.b64encode(b\"<username>:<password>\")\nproxy = httpcore.HTTPProxy(\n    proxy_url=\"http://127.0.0.1:8080/\",\n    proxy_headers={\"Proxy-Authorization\": b\"Basic \" + auth}\n)\n
"},{"location":"proxies/#proxy-ssl","title":"Proxy SSL","text":"

The httpcore package also supports HTTPS proxies for http and https destinations.

HTTPS proxies can be used in the same way that HTTP proxies are.

proxy = httpcore.HTTPProxy(proxy_url=\"https://127.0.0.1:8080/\")\n

Also, when using HTTPS proxies, you may need to configure the SSL context, which you can do with the proxy_ssl_context argument.

import ssl\nimport httpcore\n\nproxy_ssl_context = ssl.create_default_context()\nproxy_ssl_context.check_hostname = False\n\nproxy = httpcore.HTTPProxy('https://127.0.0.1:8080/', proxy_ssl_context=proxy_ssl_context)\n

It is important to note that the ssl_context argument is always used for the remote connection, and the proxy_ssl_context argument is always used for the proxy connection.

"},{"location":"proxies/#http-versions","title":"HTTP Versions","text":"

If you use proxies, keep in mind that the httpcore package only supports proxies to HTTP/1.1 servers.

"},{"location":"proxies/#socks-proxy-support","title":"SOCKS proxy support","text":"

The httpcore package also supports proxies using the SOCKS5 protocol.

Make sure to install the optional dependancy using pip install httpcore[socks].

The SOCKSProxy class should be using instead of a standard connection pool:

import httpcore\n\n# Note that the SOCKS port is 1080.\nproxy = httpcore.SOCKSProxy(proxy_url=\"socks5://127.0.0.1:1080/\")\nr = proxy.request(\"GET\", \"https://www.example.com/\")\n

Authentication via SOCKS is also supported:

import httpcore\n\nproxy = httpcore.SOCKSProxy(\n    proxy_url=\"socks5://127.0.0.1:8080/\",\n    proxy_auth=(\"<username>\", \"<password>\")\n)\nr = proxy.request(\"GET\", \"https://www.example.com/\")\n
"},{"location":"proxies/#reference","title":"Reference","text":""},{"location":"proxies/#httpcorehttpproxy","title":"httpcore.HTTPProxy","text":"

A connection pool that sends requests via an HTTP proxy.

"},{"location":"proxies/#httpcore.HTTPProxy.__init__","title":"__init__(self, proxy_url, proxy_auth=None, proxy_headers=None, ssl_context=None, proxy_ssl_context=None, max_connections=10, max_keepalive_connections=None, keepalive_expiry=None, http1=True, http2=False, retries=0, local_address=None, uds=None, network_backend=None, socket_options=None) special","text":"

A connection pool for making HTTP requests.

Parameters:

Name Type Description Default proxy_url Union[httpcore.URL, bytes, str]

The URL to use when connecting to the proxy server. For example \"http://127.0.0.1:8080/\".

required proxy_auth Optional[Tuple[Union[bytes, str], Union[bytes, str]]]

Any proxy authentication as a two-tuple of (username, password). May be either bytes or ascii-only str.

None proxy_headers Union[Mapping[Union[bytes, str], Union[bytes, str]], Sequence[Tuple[Union[bytes, str], Union[bytes, str]]]]

Any HTTP headers to use for the proxy requests. For example {\"Proxy-Authorization\": \"Basic <username>:<password>\"}.

None ssl_context Optional[ssl.SSLContext]

An SSL context to use for verifying connections. If not specified, the default httpcore.default_ssl_context() will be used.

None proxy_ssl_context Optional[ssl.SSLContext]

The same as ssl_context, but for a proxy server rather than a remote origin.

None max_connections Optional[int]

The maximum number of concurrent HTTP connections that the pool should allow. Any attempt to send a request on a pool that would exceed this amount will block until a connection is available.

10 max_keepalive_connections Optional[int]

The maximum number of idle HTTP connections that will be maintained in the pool.

None keepalive_expiry Optional[float]

The duration in seconds that an idle HTTP connection may be maintained for before being expired from the pool.

None http1 bool

A boolean indicating if HTTP/1.1 requests should be supported by the connection pool. Defaults to True.

True http2 bool

A boolean indicating if HTTP/2 requests should be supported by the connection pool. Defaults to False.

False retries int

The maximum number of retries when trying to establish a connection.

0 local_address Optional[str]

Local address to connect from. Can also be used to connect using a particular address family. Using local_address=\"0.0.0.0\" will connect using an AF_INET address (IPv4), while using local_address=\"::\" will connect using an AF_INET6 address (IPv6).

None uds Optional[str]

Path to a Unix Domain Socket to use instead of TCP sockets.

None network_backend Optional[httpcore.NetworkBackend]

A backend instance to use for handling network I/O.

None"},{"location":"quickstart/","title":"Quickstart","text":"

For convenience, the httpcore package provides a couple of top-level functions that you can use for sending HTTP requests. You probably don't want to integrate against functions if you're writing a library that uses httpcore, but you might find them useful for testing httpcore from the command-line, or if you're writing a simple script that doesn't require any of the connection pooling or advanced configuration that httpcore offers.

"},{"location":"quickstart/#sending-a-request","title":"Sending a request","text":"

We'll start off by sending a request...

import httpcore\n\nresponse = httpcore.request(\"GET\", \"https://www.example.com/\")\n\nprint(response)\n# <Response [200]>\nprint(response.status)\n# 200\nprint(response.headers)\n# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...]\nprint(response.content)\n# b'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>\\n\\n<meta charset=\"utf-8\"/>\\n ...'\n
"},{"location":"quickstart/#request-headers","title":"Request headers","text":"

Request headers may be included either in a dictionary style, or as a list of two-tuples.

import httpcore\nimport json\n\nheaders = {'User-Agent': 'httpcore'}\nr = httpcore.request('GET', 'https://httpbin.org/headers', headers=headers)\n\nprint(json.loads(r.content))\n# {\n#     'headers': {\n#         'Host': 'httpbin.org',\n#         'User-Agent': 'httpcore',\n#         'X-Amzn-Trace-Id': 'Root=1-616ff5de-5ea1b7e12766f1cf3b8e3a33'\n#     }\n# }\n

The keys and values may either be provided as strings or as bytes. Where strings are provided they may only contain characters within the ASCII range chr(0) - chr(127). To include characters outside this range you must deal with any character encoding explicitly, and pass bytes as the header keys/values.

The Host header will always be automatically included in any outgoing request, as it is strictly required to be present by the HTTP protocol.

Note that the X-Amzn-Trace-Id header shown in the example above is not an outgoing request header, but has been added by a gateway server.

"},{"location":"quickstart/#request-body","title":"Request body","text":"

A request body can be included either as bytes...

import httpcore\nimport json\n\nr = httpcore.request('POST', 'https://httpbin.org/post', content=b'Hello, world')\n\nprint(json.loads(r.content))\n# {\n#     'args': {},\n#     'data': 'Hello, world',\n#     'files': {},\n#     'form': {},\n#     'headers': {\n#         'Host': 'httpbin.org',\n#         'Content-Length': '12',\n#         'X-Amzn-Trace-Id': 'Root=1-61700258-00e338a124ca55854bf8435f'\n#     },\n#     'json': None,\n#     'origin': '68.41.35.196',\n#     'url': 'https://httpbin.org/post'\n# }\n

Or as an iterable that returns bytes...

import httpcore\nimport json\n\nwith open(\"hello-world.txt\", \"rb\") as input_file:\n    r = httpcore.request('POST', 'https://httpbin.org/post', content=input_file)\n\nprint(json.loads(r.content))\n# {\n#     'args': {},\n#     'data': 'Hello, world',\n#     'files': {},\n#     'form': {},\n#     'headers': {\n#         'Host': 'httpbin.org',\n#         'Transfer-Encoding': 'chunked',\n#         'X-Amzn-Trace-Id': 'Root=1-61700258-00e338a124ca55854bf8435f'\n#     },\n#     'json': None,\n#     'origin': '68.41.35.196',\n#     'url': 'https://httpbin.org/post'\n# }\n

When a request body is included, either a Content-Length header or a Transfer-Encoding: chunked header will be automatically included.

The Content-Length header is used when passing bytes, and indicates an HTTP request with a body of a pre-determined length.

The Transfer-Encoding: chunked header is the mechanism that HTTP/1.1 uses for sending HTTP request bodies without a pre-determined length.

"},{"location":"quickstart/#streaming-responses","title":"Streaming responses","text":"

When using the httpcore.request() function, the response body will automatically be read to completion, and made available in the response.content attribute.

Sometimes you may be dealing with large responses and not want to read the entire response into memory. The httpcore.stream() function provides a mechanism for sending a request and dealing with a streaming response:

import httpcore\n\nwith httpcore.stream('GET', 'https://example.com') as response:\n    for chunk in response.iter_stream():\n        print(f\"Downloaded: {chunk}\")\n

Here's a more complete example that demonstrates downloading a response:

import httpcore\n\nwith httpcore.stream('GET', 'https://speed.hetzner.de/100MB.bin') as response:\n    with open(\"download.bin\", \"wb\") as output_file:\n        for chunk in response.iter_stream():\n            output_file.write(chunk)\n

The httpcore.stream() API also allows you to conditionally read the response...

import httpcore\n\nwith httpcore.stream('GET', 'https://example.com') as response:\n    content_length = [int(v) for k, v in response.headers if k.lower() == b'content-length'][0]\n    if content_length > 100_000_000:\n        raise Exception(\"Response too large.\")\n    response.read()  # `response.content` is now available.\n
"},{"location":"quickstart/#reference","title":"Reference","text":""},{"location":"quickstart/#httpcorerequest","title":"httpcore.request()","text":"

Sends an HTTP request, returning the response.

response = httpcore.request(\"GET\", \"https://www.example.com/\")\n

Parameters:

Name Type Description Default method Union[bytes, str]

The HTTP method for the request. Typically one of \"GET\", \"OPTIONS\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", or \"DELETE\".

required url Union[httpcore.URL, bytes, str]

The URL of the HTTP request. Either as an instance of httpcore.URL, or as str/bytes.

required headers Union[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers. Either as a dictionary of str/bytes, or as a list of two-tuples of str/bytes.

None content Union[bytes, Iterator[bytes]]

The content of the request body. Either as bytes, or as a bytes iterator.

None extensions Optional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the request. Possible keys include \"timeout\".

None

Returns:

Type Description Response

An instance of httpcore.Response.

"},{"location":"quickstart/#httpcorestream","title":"httpcore.stream()","text":"

Sends an HTTP request, returning the response within a content manager.

with httpcore.stream(\"GET\", \"https://www.example.com/\") as response:\n    ...\n

When using the stream() function, the body of the response will not be automatically read. If you want to access the response body you should either use content = response.read(), or for chunk in response.iter_content().

Parameters:

Name Type Description Default method Union[bytes, str]

The HTTP method for the request. Typically one of \"GET\", \"OPTIONS\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", or \"DELETE\".

required url Union[httpcore.URL, bytes, str]

The URL of the HTTP request. Either as an instance of httpcore.URL, or as str/bytes.

required headers Union[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers. Either as a dictionary of str/bytes, or as a list of two-tuples of str/bytes.

None content Union[bytes, Iterator[bytes]]

The content of the request body. Either as bytes, or as a bytes iterator.

None extensions Optional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the request. Possible keys include \"timeout\".

None

Returns:

Type Description Iterator[httpcore.Response]

An instance of httpcore.Response.

"},{"location":"requests-responses-urls/","title":"Requests, Responses, and URLs","text":"

TODO

"},{"location":"requests-responses-urls/#requests","title":"Requests","text":"

Request instances in httpcore are deliberately simple, and only include the essential information required to represent an HTTP request.

Properties on the request are plain byte-wise representations.

>>> request = httpcore.Request(\"GET\", \"https://www.example.com/\")\n>>> request.method\nb\"GET\"\n>>> request.url\nhttpcore.URL(scheme=b\"https\", host=b\"www.example.com\", port=None, target=b\"/\")\n>>> request.headers\n[(b'Host', b'www.example.com')]\n>>> request.stream\n<httpcore.ByteStream [0 bytes]>\n

The interface is liberal in the types that it accepts, but specific in the properties that it uses to represent them. For example, headers may be specified as a dictionary of strings, but internally are represented as a list of (byte, byte) tuples.

```python

headers = {\"User-Agent\": \"custom\"} request = httpcore.Request(\"GET\", \"https://www.example.com/\", headers=headers) request.headers [(b'Host', b'www.example.com'), (b\"User-Agent\", b\"custom\")]

"},{"location":"requests-responses-urls/#responses","title":"Responses","text":"

...

"},{"location":"requests-responses-urls/#urls","title":"URLs","text":"

...

"},{"location":"requests-responses-urls/#reference","title":"Reference","text":""},{"location":"requests-responses-urls/#httpcorerequest","title":"httpcore.Request","text":"

An HTTP request.

"},{"location":"requests-responses-urls/#httpcore.Request.__init__","title":"__init__(self, method, url, *, headers=None, content=None, extensions=None) special","text":"

Parameters:

Name Type Description Default method Union[bytes, str]

The HTTP request method, either as a string or bytes. For example: GET.

required url Union[httpcore.URL, bytes, str]

The request URL, either as a URL instance, or as a string or bytes. For example: \"https://www.example.com\".

required headers Union[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP request headers.

None content Union[bytes, Iterable[bytes], AsyncIterable[bytes]]

The content of the response body.

None extensions Optional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the request. Possible keys include \"timeout\", and \"trace\".

None"},{"location":"requests-responses-urls/#httpcoreresponse","title":"httpcore.Response","text":"

An HTTP response.

"},{"location":"requests-responses-urls/#httpcore.Response.__init__","title":"__init__(self, status, *, headers=None, content=None, extensions=None) special","text":"

Parameters:

Name Type Description Default status int

The HTTP status code of the response. For example 200.

required headers Union[Sequence[Tuple[Union[bytes, str], Union[bytes, str]]], Mapping[Union[bytes, str], Union[bytes, str]]]

The HTTP response headers.

None content Union[bytes, Iterable[bytes], AsyncIterable[bytes]]

The content of the response body.

None extensions Optional[MutableMapping[str, Any]]

A dictionary of optional extra information included on the responseself.Possible keys include \"http_version\", \"reason_phrase\", and \"network_stream\".

None"},{"location":"requests-responses-urls/#httpcoreurl","title":"httpcore.URL","text":"

Represents the URL against which an HTTP request may be made.

The URL may either be specified as a plain string, for convienence:

url = httpcore.URL(\"https://www.example.com/\")\n

Or be constructed with explicitily pre-parsed components:

url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')\n

Using this second more explicit style allows integrations that are using httpcore to pass through URLs that have already been parsed in order to use libraries such as rfc-3986 rather than relying on the stdlib. It also ensures that URL parsing is treated identically at both the networking level and at any higher layers of abstraction.

The four components are important here, as they allow the URL to be precisely specified in a pre-parsed format. They also allow certain types of request to be created that could not otherwise be expressed.

For example, an HTTP request to http://www.example.com/ forwarded via a proxy at http://localhost:8080...

# Constructs an HTTP request with a complete URL as the target:\n# GET https://www.example.com/ HTTP/1.1\nurl = httpcore.URL(\n    scheme=b'http',\n    host=b'localhost',\n    port=8080,\n    target=b'https://www.example.com/'\n)\nrequest = httpcore.Request(\n    method=\"GET\",\n    url=url\n)\n

Another example is constructing an OPTIONS * request...

# Constructs an 'OPTIONS *' HTTP request:\n# OPTIONS * HTTP/1.1\nurl = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')\nrequest = httpcore.Request(method=\"OPTIONS\", url=url)\n

This kind of request is not possible to formulate with a URL string, because the / delimiter is always used to demark the target from the host/port portion of the URL.

For convenience, string-like arguments may be specified either as strings or as bytes. However, once a request is being issue over-the-wire, the URL components are always ultimately required to be a bytewise representation.

In order to avoid any ambiguity over character encodings, when strings are used as arguments, they must be strictly limited to the ASCII range chr(0)-chr(127). If you require a bytewise representation that is outside this range you must handle the character encoding directly, and pass a bytes instance.

"},{"location":"requests-responses-urls/#httpcore.URL.__init__","title":"__init__(self, url='', *, scheme=b'', host=b'', port=None, target=b'') special","text":"

Parameters:

Name Type Description Default url Union[bytes, str]

The complete URL as a string or bytes.

'' scheme Union[bytes, str]

The URL scheme as a string or bytes. Typically either \"http\" or \"https\".

b'' host Union[bytes, str]

The URL host as a string or bytes. Such as \"www.example.com\".

b'' port Optional[int]

The port to connect to. Either an integer or None.

None target Union[bytes, str]

The target of the HTTP request. Such as \"/items?search=red\".

b''"},{"location":"table-of-contents/","title":"API Reference","text":"
  • Quickstart
    • httpcore.request()
    • httpcore.stream()
  • Requests, Responses, and URLs
    • httpcore.Request
    • httpcore.Response
    • httpcore.URL
  • Connection Pools
    • httpcore.ConnectionPool
  • Proxies
    • httpcore.HTTPProxy
  • Connections
    • httpcore.HTTPConnection
    • httpcore.HTTP11Connection
    • httpcore.HTTP2Connection
  • Async Support
    • httpcore.AsyncConnectionPool
    • httpcore.AsyncHTTPProxy
    • httpcore.AsyncHTTPConnection
    • httpcore.AsyncHTTP11Connection
    • httpcore.AsyncHTTP2Connection
  • Network Backends
    • Sync
      • httpcore.backends.sync.SyncBackend
      • httpcore.backends.mock.MockBackend
    • Async
      • httpcore.backends.auto.AutoBackend
      • httpcore.backends.asyncio.AsyncioBackend
      • httpcore.backends.trio.TrioBackend
      • httpcore.backends.mock.AsyncMockBackend
    • Base interfaces
      • httpcore.backends.base.NetworkBackend
      • httpcore.backends.base.AsyncNetworkBackend
  • Exceptions
    • httpcore.TimeoutException
      • httpcore.PoolTimeout
      • httpcore.ConnectTimeout
      • httpcore.ReadTimeout
      • httpcore.WriteTimeout
    • httpcore.NetworkError
      • httpcore.ConnectError
      • httpcore.ReadError
      • httpcore.WriteError
    • httpcore.ProtocolError
      • httpcore.RemoteProtocolError
      • httpcore.LocalProtocolError
    • httpcore.ProxyError
    • httpcore.UnsupportedProtocol
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..b991d022 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,68 @@ + + + + https://www.encode.io/httpcore/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/async/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/connection-pools/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/connections/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/exceptions/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/extensions/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/http2/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/logging/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/network-backends/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/proxies/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/quickstart/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/requests-responses-urls/ + 2023-09-08 + daily + + + https://www.encode.io/httpcore/table-of-contents/ + 2023-09-08 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..27b443e4059de3be6f06ad4624342d097829b094 GIT binary patch literal 315 zcmV-B0mS|viwFqSE&F5w|8r?{Wo=<_E_iKh0M(YkZi6rkhVOj}#65(uOFL0|+Y@XL zkOmhbLQG>b=-V$)G;!Wx%5sVG%lY$3R-BZVxwpv|crxhD=}VTU2{?_lam_jX{CF3~ z^imzl(OUveLb9WC+AS}SJ z&1O9lGJ|qaHF}VxT$1*C6mgGrRpiC1$WJ0ahLla)rRi3=25uhiRjrJDlyav$VdM|Z zFSryvoQ{@GqjR7+R8tIymi_@~yo@kw7-C0ca1=G-5){$WS!|ln?4sp!z!UmTT$S#? z)$x{j@HiWYm+w9r-4QF#(IN($5r|26pdnmoAhA5W(ekpoS`c^>p_zv^#D74#PT?z3 NeFG708%(+g007!@leYi> literal 0 HcmV?d00001 diff --git a/table-of-contents/index.html b/table-of-contents/index.html new file mode 100644 index 00000000..aaca96c6 --- /dev/null +++ b/table-of-contents/index.html @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + API Reference - HTTPCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

API Reference

+
    +
  • Quickstart
      +
    • httpcore.request()
    • +
    • httpcore.stream()
    • +
    +
  • +
  • Requests, Responses, and URLs
      +
    • httpcore.Request
    • +
    • httpcore.Response
    • +
    • httpcore.URL
    • +
    +
  • +
  • Connection Pools
      +
    • httpcore.ConnectionPool
    • +
    +
  • +
  • Proxies
      +
    • httpcore.HTTPProxy
    • +
    +
  • +
  • Connections
      +
    • httpcore.HTTPConnection
    • +
    • httpcore.HTTP11Connection
    • +
    • httpcore.HTTP2Connection
    • +
    +
  • +
  • Async Support
      +
    • httpcore.AsyncConnectionPool
    • +
    • httpcore.AsyncHTTPProxy
    • +
    • httpcore.AsyncHTTPConnection
    • +
    • httpcore.AsyncHTTP11Connection
    • +
    • httpcore.AsyncHTTP2Connection
    • +
    +
  • +
  • Network Backends
      +
    • Sync
        +
      • httpcore.backends.sync.SyncBackend
      • +
      • httpcore.backends.mock.MockBackend
      • +
      +
    • +
    • Async
        +
      • httpcore.backends.auto.AutoBackend
      • +
      • httpcore.backends.asyncio.AsyncioBackend
      • +
      • httpcore.backends.trio.TrioBackend
      • +
      • httpcore.backends.mock.AsyncMockBackend
      • +
      +
    • +
    • Base interfaces
        +
      • httpcore.backends.base.NetworkBackend
      • +
      • httpcore.backends.base.AsyncNetworkBackend
      • +
      +
    • +
    +
  • +
  • Exceptions
      +
    • httpcore.TimeoutException
        +
      • httpcore.PoolTimeout
      • +
      • httpcore.ConnectTimeout
      • +
      • httpcore.ReadTimeout
      • +
      • httpcore.WriteTimeout
      • +
      +
    • +
    • httpcore.NetworkError
        +
      • httpcore.ConnectError
      • +
      • httpcore.ReadError
      • +
      • httpcore.WriteError
      • +
      +
    • +
    • httpcore.ProtocolError
        +
      • httpcore.RemoteProtocolError
      • +
      • httpcore.LocalProtocolError
      • +
      +
    • +
    • httpcore.ProxyError
    • +
    • httpcore.UnsupportedProtocol
    • +
    +
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file