-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path14500755551100.html
432 lines (253 loc) · 19.5 KB
/
14500755551100.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
Solving the OPTIONS Performance Issue With Single Page Apps - Junkman
</title>
<link href="atom.xml" rel="alternate" title="Junkman" type="application/atom+xml">
<link rel="stylesheet" href="asset/css/foundation.min.css" />
<link rel="stylesheet" href="asset/css/docs.css" />
<script src="asset/js/vendor/modernizr.js"></script>
<script src="asset/js/vendor/jquery.js"></script>
<script src="asset/highlightjs/highlight.pack.js"></script>
<link href="asset/highlightjs/styles/github.css" media="screen, projection" rel="stylesheet" type="text/css">
<script>hljs.initHighlightingOnLoad();</script>
<script type="text/javascript">
function before_search(){
var searchVal = 'site:panlw.github.io ' + document.getElementById('search_input').value;
document.getElementById('search_q').value = searchVal;
return true;
}
</script>
</head>
<body class="antialiased hide-extras">
<div class="marketing off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<nav class="top-bar docs-bar hide-for-small" data-topbar>
<section class="top-bar-section">
<div class="row">
<div style="position: relative;width:100%;"><div style="position: absolute; width:100%;">
<ul id="main-menu" class="left">
<li id=""><a target="self" href="index.html">Home</a></li>
<li id=""><a target="_self" href="archives.html">Archives</a></li>
</ul>
<ul class="right" id="search-wrap">
<li>
<form target="_blank" onsubmit="return before_search();" action="http://google.com/search" method="get">
<input type="hidden" id="search_q" name="q" value="" />
<input tabindex="1" type="search" id="search_input" placeholder="Search"/>
</form>
</li>
</ul>
</div></div>
</div>
</section>
</nav>
<nav class="tab-bar show-for-small">
<a href="javascript:void(0)" class="left-off-canvas-toggle menu-icon">
<span> Junkman</span>
</a>
</nav>
<aside class="left-off-canvas-menu">
<ul class="off-canvas-list">
<li><a href="index.html">HOME</a></li>
<li><a href="archives.html">Archives</a></li>
<li><a href="about.html">ABOUT</a></li>
<li><label>Categories</label></li>
<li><a href="Infra.html">Infra</a></li>
<li><a href="Coding.html">Coding</a></li>
<li><a href="Modeling.html">Modeling</a></li>
<li><a href="Archtecting.html">Archtecting</a></li>
</ul>
</aside>
<a class="exit-off-canvas" href="#"></a>
<section id="main-content" role="main" class="scroll-container">
<script type="text/javascript">
$(function(){
$('#menu_item_index').addClass('is_active');
});
</script>
<div class="row">
<div class="large-8 medium-8 columns">
<div class="markdown-body article-wrap">
<div class="article">
<h1>Solving the OPTIONS Performance Issue With Single Page Apps</h1>
<div class="read-more clearfix">
<span class="date">2015/12/14</span>
<span>posted in </span>
<span class="posted-in"><a href='Node.js.html'>Node.js</a></span>
<span class="comments">
</span>
</div>
</div><!-- article -->
<div class="article-content">
<p>@see <a href="https://dzone.com/articles/solving-the-options-performance-issue-with-single">https://dzone.com/articles/solving-the-options-performance-issue-with-single</a></p>
<p>Single-page applications are all the rage these days. VariousnewJavaScriptframeworks make it very easy to write complex applications in JavaScript, with most of the MVC pattern running in the browser. This of course comes with its own challenges, and at SOASTA, we’re always up for those.</p>
<p>Back in May, my fellow boomerang developer Nic Jansmaexplained how we’ve hacked boomerang to measure the performance of single-page applications. Today, let’s talk about another issue: the performance of SPAs when CORS comes into play.</p>
<p>Matt Aimonetti, co-founder of Splice, tech entrepreneur, and regular conference speaker recently ran into this issue, and has agreed to co-author this post, sharing his experience.</p>
<p>But First, Some History…<br/>
Way back in the late 1990s, Microsoft introduced the XMLHttpRequest object to make background HTTP requests from JavaScript without disrupting the flow of a page; however, because no other browser (aka Netscape) supported it, it went largely unnoticed.</p>
<p>On April 1, 2004, as part of an elaborate April Fool’s joke that’s ongoing to this day, Google released what by many is considered the first widely known single-page app (we called them Rich Internet Applications back then). They called it Gmail, and web developers everywhere started looking through the code to find out how they did it.</p>
<p>Pub Standards, Dec 2005<br/>
In 2005, Jesse James Garrett coined the term AJAX to describe the communications method used by these apps, and standardistas everywhere decided to create best practices to avoid falling down the rabbit hole of unmaintainable code that failed accessibility standards we’d worked so hard to create. Or as Thomas Vander Waal put it:</p>
<p>“It must degrade well. It must still be accessible. It must be usable. If not, it is a cool useless piece of rubbish for some or many people.”<br/>
With that, Jeremy Keith introduced us to Hijax.</p>
<p>Now early on, browser developers realised they couldn’t just allow you to make XHR calls anywhere, because that would allow attackers to steal third-party information in the background relying on a user’s logged in cookies, so XHR was limited to the same-origin policy, i.e., you could only make an XHR call to a server that was on the same domain as the page you were making the call from. You could change document.domain to make this domain check slightly less restrictive, but it was still limited to a parent domain of the current page.</p>
<h2 id="toc_0">Enter CORS</h2>
<p>This security model kinda worked, but we were also entering the age of Web APIs, where third parties wanted random websites to be able to pull data from their servers, potentially using the user’s logged in cookies to get personalised information. But more importantly, websites wanted to be able to use their own APIs that were potentially served from a different domain (like their CDN). Things like dynamic script nodes and JSON-P worked, but they broke the security model, making it harder to protect these services from Cross-Site Request Forgeries.</p>
<p>The web standards group stepped in to introduce the concept of Cross Origin Resource Sharing, or CORS, which states that a server can specify via the Access-Control-Allow-Origin header, which Origins its content is allowed to be shared with.</p>
<h2 id="toc_1">Preflighted Requests</h2>
<p>Unfortunately, every cool specification also comes with unexpected security considerations. For CORS, this is a preflighted request.</p>
<p>According to MDN,</p>
<p>In particular, a request is preflighted if:</p>
<p>It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.<br/>
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)<br/>
The idea is to ask the server for permission to send custom headers, and as others have found, the commonly used X-Requested-With HTTP header tends to trigger this.</p>
<h2 id="toc_2">Flowchart for Preflighted XHR</h2>
<p>At Splice, we have a Go backend and we started out with a Rails frontend talking to our APIs. As time went by, the amount of JQuery code started to be hard to maintain and Rails rendering was becoming a bottleneck. We ported the frontend from Rails to Angular and everything seemed to be fine… until we started hearing complaints from our non-US users saying that parts of the website were slow for them. It turned out that these were the parts where we made many API calls to access user specific/signed/encrypted resources. Inspecting the network calls via a VPN connection, we noticed that main issue was latency.</p>
<p>The latency between Sweden and California isn’t great, but what’s worse was that each API call had to wait on the preflight OPTIONS call before dispatching the actual request. Our API’s response time is fast (sub 10ms), yet some users would see response times north of 500ms!</p>
<p>This second HTTP OPTIONS request, doubles the latency for getting your data… and Real Users™ hate latency.</p>
<p>So, how do we get rid of this extra request?</p>
<h3 id="toc_3">X-Requested-With</h3>
<p>To get to that, we need to understand why developers use the X-Requested-With header, which brings us all the way back to 2005 with all those best practices around Ajax. In order to use canonical URIs for all resources, we use the same URL for the full page request as well as the XHR request, and use the X-Requested-With header to distinguish between the requester.</p>
<h2 id="toc_4">There are a couple of problems with this, though:</h2>
<p>X-Requested-With is a custom header hoping to be a de-facto standard<br/>
We’re sending different responses for the same URL based on the type of requester rather than its advertised capabilities or the requested response format.<br/>
This starts to smell a lot like the late ‘90s, when we served different HTML to Internet Explorer and Netscape.</p>
<p>The solution for these problems is to use standard headers that correctly advertise capabilities or requested response format, and it turns out that the HTTP spec does have a standard header for just this purpose.</p>
<p>Other Custom Headers<br/>
In recent versions of Angular and JQuery, this header was actually removed by default unless explicitly added, so it wasn’t an issue for Splice, but because we used to send this header (via JQuery), we wrongly assumed that preflight requests was unavoidable.</p>
<p>We did, however have other custom headers that we used to report the version (git hash) of the JavaScript code making the request. These headers had the same effect, since they failed the custom headers check for the preflight.</p>
<h2 id="toc_5">The Accept Header</h2>
<p>The Accept header, described in section 14.1 of RFC 2616, allows the caller to specify the content types it is willing to receive. By default, the browser will include text/html as the preferred acceptable type. When making our XHR request, we could specify application/json, application/xml, text/csv or anything else as the only acceptable content type.</p>
<p>Your server application can then look at the Accept header to decide whether to respond with the full HTML or with a JSON representation of the data or something else. The technical term for this is called content negotiation.</p>
<p>Other implementations add a query string parameter to the URL specifying the requested content type or other parameters related to the client library like a version or a hash.</p>
<h2 id="toc_6">Caching</h2>
<p>Responses to these requests can be cached if they have the appropriate cache-control headers. It’s important to make sure that the server uses the Vary header to specify that the Accept header was used to generate negotiated content content, and therefore should be part of the cache key, both in the browser, as well as in intermediate proxies.</p>
<h2 id="toc_7">Summary</h2>
<p>Cross-domain XMLHttpRequests work if the server supports the appropriate CORS headers.<br/>
Adding custom headers or using a non-standard content-type forces the browser to issue a preflight OPTIONS request to determine if these are acceptable or not, and this effectively doubles the latency of fetching data.<br/>
Avoid custom HTTP headers, and use standard headers like Accept for content negotiated responses instead.<br/>
Use the Vary header to tell clients and intermediates that the Accept header is important for caching.<br/>
It’s important to learn from history so that we do not repeat old mistakes.<br/>
Notes<br/>
YUI does not add the X-Requested-With header when making XHRs.<br/>
JQuery does not add the X-Requested-With header for cross-domain XHRs<br/>
Dojo does add the X-Requested-With header for all XHRs, and you need to explicitly clear it<br/>
As of version 1.1.1 (2012), AngularJS no longer adds the X-Requested-With and X-XSFR-TOKEN.<br/>
Prototype.js does add the X-Requested-With header along with other custom headers for XHRs and you need to explicitly clear it.</p>
<h2 id="toc_8">References</h2>
<ul>
<li>CORS at MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS</a></li>
<li>CORS by Nicholas Zakas: <a href="http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/">http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/</a></li>
<li>Browsers that support CORS: <a href="http://caniuse.com/#feat=cors">http://caniuse.com/#feat=cors</a></li>
<li>RFC 2616 (HTTP/1.1): <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">http://www.w3.org/Protocols/rfc2616/rfc2616.txt</a></li>
<li>Remy Sharp discussing this problem: <a href="https://remysharp.com/2011/04/21/getting-cors-working">https://remysharp.com/2011/04/21/getting-cors-working</a></li>
<li>Very good talk by @jaffathecake about modern progressive enhancement:<a href="http://www.fivesimplesteps.com/products/modern-progressive-enhancement">http://www.fivesimplesteps.com/products/modern-progressive-enhancement</a></li>
</ul>
</div>
<div class="row">
<div class="large-6 columns">
<p class="text-left" style="padding:15px 0px;">
<a href="14500759271142.html"
title="Previous Post: Create a character voting app using React, Node.js, MongoDB and Socket.IO">« Create a character voting app using React, Node.js, MongoDB and Socket.IO</a>
</p>
</div>
<div class="large-6 columns">
<p class="text-right" style="padding:15px 0px;">
<a href="14500754837479.html"
title="Next Post: Starting and Stopping Background Services with Homebrew">Starting and Stopping Background Services with Homebrew »</a>
</p>
</div>
</div>
<div class="comments-wrap">
<div class="share-comments">
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5ae58078c0d7b2ab"></script>
</div>
</div>
</div><!-- article-wrap -->
</div><!-- large 8 -->
<div class="large-4 medium-4 columns">
<div class="hide-for-small">
<div id="sidebar" class="sidebar">
<div id="site-info" class="site-info">
<div class="site-a-logo"><img src="./asset/img/logo.jpg" /></div>
<h1>Junkman</h1>
<div class="site-des">“拾荒者”一词来自凯文・凯利的《失控》中关于机器学习的故事(“收集癖好机”如何完成他的收集工作)。</div>
<div class="social">
<a target="_blank" class="github" target="_blank" href="https://github.com/panlw/" title="GitHub">GitHub</a>
<a target="_blank" class="rss" href="atom.xml" title="RSS">RSS</a>
</div>
</div>
<div id="site-categories" class="side-item ">
<div class="side-header">
<h2>Categories</h2>
</div>
<div class="side-content">
<p class="cat-list">
<a href="Infra.html"><strong>Infra</strong></a>
<a href="Coding.html"><strong>Coding</strong></a>
<a href="Modeling.html"><strong>Modeling</strong></a>
<a href="Archtecting.html"><strong>Archtecting</strong></a>
</p>
</div>
</div>
<div id="site-categories" class="side-item">
<div class="side-header">
<h2>Recent Posts</h2>
</div>
<div class="side-content">
<ul class="posts-list">
<li class="post">
<a href="15517999043443.html">The Art of Crafting Architectural Diagrams</a>
</li>
<li class="post">
<a href="15517997955971.html">为什么说我们需要软件架构图?</a>
</li>
<li class="post">
<a href="15516128677869.html">DNS Servers That Offer Privacy and Filtering</a>
</li>
<li class="post">
<a href="15516123108194.html">Airbnb's Migration from Monolith to Services</a>
</li>
<li class="post">
<a href="15516097487470.html">Events As First-Class Citizens</a>
</li>
</ul>
</div>
</div>
</div><!-- sidebar -->
</div><!-- hide for small -->
</div><!-- large 4 -->
</div><!-- row -->
<div class="page-bottom clearfix">
<div class="row">
<p class="copyright">Copyright © 2015
Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,
Theme used <a target="_blank" href="http://github.com">GitHub CSS</a>.</p>
</div>
</div>
</section>
</div>
</div>
<script src="asset/js/foundation.min.js"></script>
<script>
$(document).foundation();
function fixSidebarHeight(){
var w1 = $('.markdown-body').height();
var w2 = $('#sidebar').height();
if (w1 > w2) { $('#sidebar').height(w1); };
}
$(function(){
fixSidebarHeight();
})
$(window).load(function(){
fixSidebarHeight();
});
</script>
<script src="asset/chart/all-min.js"></script><script type="text/javascript">$(function(){ var mwebii=0; var mwebChartEleId = 'mweb-chart-ele-'; $('pre>code').each(function(){ mwebii++; var eleiid = mwebChartEleId+mwebii; if($(this).hasClass('language-sequence')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = Diagram.parse($(this).text()); diagram.drawSVG(eleiid,{theme: 'simple'}); }else if($(this).hasClass('language-flow')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = flowchart.parse($(this).text()); diagram.drawSVG(eleiid); } });});</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><script type="text/x-mathjax-config">MathJax.Hub.Config({TeX: { equationNumbers: { autoNumber: "AMS" } }});</script>
</body>
</html>