HTTP POST using XHR with Chunked Transfer Encoding - javascript

I have a REST API that accepts an Audio file via an HTTP Post. The API has support for Transfer-Encoding: chunked request header so that the file can be uploaded in pieces as it is being created from a recorder running on the client. This way the server can start processing the file as it arrives for improved performance. For example:
HTTP 1.1 POST .../v1/processAudio
Transfer-Encoding: chunked
[Chunk 1 256 Bytes] (server starts processing when arrives)
[Chunk 2 256 Bytes]
[Chunk 3 256 Bytes]
...
The audio files are typically short and are around 10K to 100K in size. I have C# and Java code that is working so I know that API works. However, I cannot seem to get the recording and upload working in a browser using javascript.
Here is my Test Code that does a POST to localhost with Transfer-Encoding:
<html>
<script type="text/javascript">
function streamUpload() {
var blob = new Blob(['GmnQPBU+nyRGER4JPAW4DjDQC19D']);
var xhr = new XMLHttpRequest();
// Add any event handlers here...
xhr.open('POST', '/', true);
xhr.setRequestHeader("Transfer-Encoding", "chunked");
xhr.send(blob);
}
</script>
<body>
<div id='demo'>Test Chunked Upload using XHR</div>
<button onclick="streamUpload()">Start Upload</button>
</body>
</html>
The problem is that i'm receiving the following Error in Chrome
Refused to set unsafe header "Transfer-Encoding"
streamUpload # uploadTest.html:14
onclick # uploadTest.html:24
After looking at XHR documentation i'm still confused because it does not talk about unsafe request headers. I'm wondering if its possible that XHR does not allow or implement Transfer-Encoding: chunked for HTTP POST?
I've looked at work arounds using multiple XHR.send() requests and WebSockets but both are undesirable because it will require significant changes to the server APIs which are already in place, simple, stable and working. The only issue is that we cannot seem to POST from a browser with psedo-streaming via Transfer-Encoding: chunked request header.
Any thoughts or advice would be very helpful.

As was mentioned in a comment, you're not allowed to set that header as it's controlled by the user agent.
For the full set of headers, see 4.6.2 The setRequestHeader() method from W3C XMLHttpRequest Level 1 and note that Transfer-Encoding is one of the headers that are controlled by the user agent to let it control those aspects of transport.
Accept-Charset
Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
Cookie2
Date
DNT
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via
There is a similar list in the WhatWG Fetch API Living Standard.
https://fetch.spec.whatwg.org/#terminology-headers

As other replies have already mentioned, you aren't allowed to set the "Transfer-Encoding" header yourself.
However, you also don't actually need to use HTTP chunked transfer encoding in order to incrementally stream a file to your server and start processing parts of it right away either. A regular HTTP POST works just fine for that. Even though it is transmitted as a single HTTP request, I believe the streaming/chunking magic happens for you at the TCP level (other people are welcome to correct me if I'm wrong on where the magic specifically happens). I can confirm this works because I've done it with node.js and Express on the backend. I'm sure it probably works with other server side technologies as well.
HTTP chunked transfer encoding is only useful when you DON'T know the size of the stream you are going to be sending in advance (such as live video, video conference calls, remote desktop sessions, chats, etc.). And for these cases WebSockets are a more widely deployed solution that solve the same problem:
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
For your use case, where you DO know the size of the file in advance you are probably better off sticking to your XmlHttpRequest and abandoning the chunked transfer encoding. Alternatively, you can give the new Fetch API a try:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

Related

Can't get basic HTTP POST function to work from localhost with Javascript [duplicate]

I am building a web API. I found whenever I use Chrome to POST, GET to my API, there is always an OPTIONS request sent before the real request, which is quite annoying. Currently, I get the server to ignore any OPTIONS requests. Now my question is what's good to send an OPTIONS request to double the server's load? Is there any way to completely stop the browser from sending OPTIONS requests?
edit 2018-09-13: added some precisions about this pre-flight request and how to avoid it at the end of this reponse.
OPTIONS requests are what we call pre-flight requests in Cross-origin resource sharing (CORS).
They are necessary when you're making requests across different origins in specific situations.
This pre-flight request is made by some browsers as a safety measure to ensure that the request being done is trusted by the server.
Meaning the server understands that the method, origin and headers being sent on the request are safe to act upon.
Your server should not ignore but handle these requests whenever you're attempting to do cross origin requests.
A good resource can be found here http://enable-cors.org/
A way to handle these to get comfortable is to ensure that for any path with OPTIONS method the server sends a response with this header
Access-Control-Allow-Origin: *
This will tell the browser that the server is willing to answer requests from any origin.
For more information on how to add CORS support to your server see the following flowchart
http://www.html5rocks.com/static/images/cors_server_flowchart.png
edit 2018-09-13
CORS OPTIONS request is triggered only in somes cases, as explained in MDN docs:
Some requests don’t trigger a CORS preflight. Those are called “simple requests” in this article, though the Fetch spec (which defines CORS) doesn’t use that term. A request that doesn’t trigger a CORS preflight—a so-called “simple request”—is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (for example, Connection, User-Agent, or any of the other headers with names defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as being a “CORS-safelisted request-header”, which are:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.
No ReadableStream object is used in the request.
Have gone through this issue, below is my conclusion to this issue and my solution.
According to the CORS strategy (highly recommend you read about it) You can't just force the browser to stop sending OPTIONS request if it thinks it needs to.
There are two ways you can work around it:
Make sure your request is a "simple request"
Set Access-Control-Max-Age for the OPTIONS request
Simple request
A simple cross-site request is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (e.g. Connection, User-Agent, etc.), the only headers which are allowed to be manually set are:
Accept
Accept-Language
Content-Language
Content-Type
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
A simple request will not cause a pre-flight OPTIONS request.
Set a cache for the OPTIONS check
You can set a Access-Control-Max-Age for the OPTIONS request, so that it will not check the permission again until it is expired.
Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request.
Limitation Noted
For Chrome, the maximum seconds for Access-Control-Max-Age is 600 which is 10 minutes, according to chrome source code
Access-Control-Max-Age only works for one resource every time, for example, GET requests with same URL path but different queries will be treated as different resources. So the request to the second resource will still trigger a preflight request.
Please refer this answer on the actual need for pre-flighted OPTIONS request: CORS - What is the motivation behind introducing preflight requests?
To disable the OPTIONS request, below conditions must be satisfied for ajax request:
Request does not set custom HTTP headers like 'application/xml' or 'application/json' etc
The request method has to be one of GET, HEAD or POST. If POST, content type should be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain
Reference:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
When you have the debug console open and the Disable Cache option turned on, preflight requests will always be sent (i.e. before each and every request). if you don't disable the cache, a pre-flight request will be sent only once (per server)
Yes it's possible to avoid options request. Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can use another technology: iframe transport layer. I strongly recommend you forget about any CORS configuration and use readymade solution and it will work anywhere.
Take a look here:
https://github.com/jpillora/xdomain
And working example:
http://jpillora.com/xdomain/
For a developer who understands the reason it exists but needs to access an API that doesn't handle OPTIONS calls without auth, I need a temporary answer so I can develop locally until the API owner adds proper SPA CORS support or I get a proxy API up and running.
I found you can disable CORS in Safari and Chrome on a Mac.
Disable same origin policy in Chrome
Chrome: Quit Chrome, open an terminal and paste this command: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Safari: Disabling same-origin policy in Safari
If you want to disable the same-origin policy on Safari (I have 9.1.1), then you only need to enable the developer menu, and select "Disable Cross-Origin Restrictions" from the develop menu.
As mentioned in previous posts already, OPTIONS requests are there for a reason. If you have an issue with large response times from your server (e.g. overseas connection) you can also have your browser cache the preflight requests.
Have your server reply with the Access-Control-Max-Age header and for requests that go to the same endpoint the preflight request will have been cached and not occur anymore.
I have solved this problem like.
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: X-Requested-With');
header("HTTP/1.1 200 OK");
die();
}
It is only for development. With this I am waiting 9ms and 500ms and not 8s and 500ms. I can do that because production JS app will be on the same machine as production so there will be no OPTIONS but development is my local.
You can't but you could avoid CORS using JSONP.
After spending a whole day and a half trying to work through a similar problem I found it had to do with IIS.
My Web API project was set up as follows:
// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
//...
}
I did not have CORS specific config options in the web.config > system.webServer node like I have seen in so many posts
No CORS specific code in the global.asax or in the controller as a decorator
The problem was the app pool settings.
The managed pipeline mode was set to classic (changed it to integrated) and the Identity was set to Network Service (changed it to ApplicationPoolIdentity)
Changing those settings (and refreshing the app pool) fixed it for me.
OPTIONS request is a feature of web browsers, so it's not easy to disable it. But I found a way to redirect it away with proxy. It's useful in case that the service endpoint just cannot handle CORS/OPTIONS yet, maybe still under development, or mal-configured.
Steps:
Setup a reverse proxy for such requests with tools of choice (nginx, YARP, ...)
Create an endpoint just to handle the OPTIONS request. It might be easier to create a normal empty endpoint, and make sure it handles CORS well.
Configure two sets of rules for the proxy. One is to route all OPTIONS requests to the dummy endpoint above. Another to route all other requests to actual endpoint in question.
Update the web site to use proxy instead.
Basically this approach is to cheat browser that OPTIONS request works. Considering CORS is not to enhance security, but to relax the same-origin policy, I hope this trick could work for a while. :)
you can also use a API Manager (like Open Sources Gravitee.io) to prevent CORS issues between frontend app and backend services by manipulating headers in preflight.
Header used in response to a preflight request to indicate which HTTP headers can be used when making the actual request :
content-type
access-control-allow-header
authorization
x-requested-with
and specify the "allow-origin" = localhost:4200 for example
One solution I have used in the past - lets say your site is on mydomain.com, and you need to make an ajax request to foreigndomain.com
Configure an IIS rewrite from your domain to the foreign domain - e.g.
<rewrite>
<rules>
<rule name="ForeignRewrite" stopProcessing="true">
<match url="^api/v1/(.*)$" />
<action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
</rule>
</rules>
</rewrite>
on your mydomain.com site - you can then make a same origin request, and there's no need for any options request :)
It can be solved in case of use of a proxy that intercept the request and write the appropriate headers.
In the particular case of Varnish these would be the rules:
if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
set resp.http.Access-Control-Max-Age = "1728000";
set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
set resp.http.Content-Length = "0";
set resp.http.Content-Type = "text/plain charset=UTF-8";
set resp.status = 204;
}
}
What worked for me was to import "github.com/gorilla/handlers" and then use it this way:
router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
As soon as I executed an Ajax POST request and attaching JSON data to it, Chrome would always add the Content-Type header which was not in my previous AllowedHeaders config.

XMLHttpRequest: How to force caching?

I'm newer to XMLHttpRequests since I've previously used jQuery's AjAX method. However I need to work in a web worker and now I have to use the classic XMLHttpRequest for performance issues.
I'm trying to rebuild the cache-property from jquery. If cache should be disabled I add this:
xhr.setRequestHeader("Cache-Control", "no-cache");
But what header should I set if I want to force caching (not prevent)?
You can specify max-stale without an argument, in Cache-Control header of your request. From RFC 7234:
The max-stale request directive indicates that the client is willing to accept a response that has exceeded its freshness lifetime. If max-stale is assigned a value, then the client is willing to accept a response that has exceeded its freshness lifetime by no more than the specified number of seconds. If no value is assigned to max-stale, then the client is willing to accept a stale response of any age.
There are a variety of headers you can set to encourage caching, but they (including Cache-Control which you are using incorrectly) are response headers that must be sent by the server and not request headers.
One such example of using Cache-Control:
Cache-Control: max-age=3600
This Caching Tutorial for Web Authors and Webmasters covers them in more depth.
Please check Caching static assets
Cache-Control: public, max-age=604800, immutable

Repeated OPTIONS requests for cors / ajax requests

On my site I have an auto-suggest text input that suggests results as the user types. The results are provided by a AJAX calls to an API on a different domain. This means I have to use CORS to allow the requests.
It is all working quite well, but every time the user types a new character, the browser sends a new OPTIONS request to ensure it is authorized.
Is there a way around all these repeated options requests?
My php script receiving the requests has
header("Access-Control-Allow-Origin: http://consent.example.com");
and the requests are all originating from consent.example.com. To be clear, the authorization works just fine, and the request completes successfully, but I don't know why it needs to keep making options calls. It would make sense to me that the browser would cache this.
According to RFC 2616 ("Hypertext Transfer Protocol -- HTTP/1.1"), section 9.2:
9.2 OPTIONS
...
Responses to this method are not cacheable.
The HTTP spec explicitly disallows caching OPTIONS responses.
It is worth noting that the GET responses do not employ caching either (I see that customers?search=alex is 200 each time). This is simply because the server chooses not to send 304 responses for that request, or your browser doesn't let the server know it has a cached copy, by an If-Modified-Since or If-None-Match request header.

ajax send request with encoding gzip is not working

Ajax send request with encoding gzip (iis7) is not working below are the code for send request
can some one help me what is wrong in my code.
Thanks in advance
function sendRequest(url, callback, postData)
{
var req = createXMLHTTPObject();
if (!req) {
return;
}
var method = (postData) ? "POST" : "GET";
req.open(method, "xml/" + url, true);
req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
if (postData) {
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
req.setRequestHeader("Content-Encoding", "gzip");
}
req.onreadystatechange = function() {
}
req.send(postData);
}
Considering the security, browser does not allow you to override some headers including "Content-Encoding".
One way to transparently have the requests for your XMLHttpRequest highly compressed is to use HTTP/2 (e.g. serve your website via CloudFlare).
When using HTTP/2, then although the HTTP headers do not say Content-Encoding: gzip the underlying HTTP/2 protocol compresses everything.
It also compresses much better than gzip because:
it compresses headers
header compression uses a standard dictionary
I think data compression builds a dictionary over multiple messages (brotli - I haven't double-checked that though)
You can see if your server is using HTTP/2 by:
Open Chrome, and F12 to open developer tools
Click on the network tab
close the request inspector panel (has tabs Headers Preview Response Timing)
Right click on the Name header of the list of requests and tick Protocol
Navigate to your website and watch what protocol is used for all requests - in the protocol column you want to see h2 not http/1.1
I wouldn't recommend using JavaScript compression libraries because that causes slowdown and inefficiencies.
The problem doesn't seem to be related to header but to compression.
You don't seem to compress your postData.
If postData is already compressed, no need to try to manually set content-encoding.
If it is not, either let the browser negotiate the transfer encoding with the server (this is part of the protocol and done automatically, the server saying if it accepts it, but I think that's rarely the case) or (if you really really need to) encode it yourself. This SO question indicates a library to compress browserside : JavaScript implementation of Gzip

Removing HTTP headers from an XMLHttpRequest

I am working on an ajax long polling type application, and I would like to minimize the amount of bandwidth I am using. One of the big costs right now are the client side HTTP headers. Once I have a connection established and a session id stored on the client, I don't really want to squander any more bandwidth transferring redundant http information (such as browser type, accept encodings, etc.). Over the course of many connections, this quickly adds up to a lot of data!
I would really like to just take my XMLHttpRequest and nuke all of the headers so that only the absolute minimum gets transmitted to the server. Is it possible to do this?
You have very little control over request headers, but you can still do a few things -
Reduce the size of the cookie. In general, you only want the session id, everything else can be eliminated and stored server side.
Minimize http referrer by keeping a short URL. The longer your page url, the more data will have to be sent via the http referrer. One trick is to store data in the fragment identifier (the portion of the url after the #). The fragment identifier is never sent to the server, so you save a few bytes over there.
Some request headers are only sent if you had previous set corresponding response headers. For example, you can indirectly control the ETag and if-modified-since request headers.
You may want to consider Web Sockets. Support is pretty good (IE10+).
You may be able to override some of the standard headers using setRequestHeader() before sending the request, but it is possible the browser may not allow overriding of some and it seems there is no way to get a list of headers (besides asking the server to echo them back to you) to know which to try to override.
I think it's possible to remove all headers at least in some browsers.
Take a look at the communication between gmail/calendar apps and the backend from google in chrome (it's not the same in firefox)
it's possible google has some hidden api for the XMLHttpRequest object,
you'll see something like the below output (notice there is no request headers section):
Request URL:https://mail.google.com/mail/u/0/channel/bind?XXXXXXXXXXXXXX
Request Method:POST
Status Code:200 OK
Query String Parameters
OSID:XXXXXXXXXXXXX
OAID:XXXXXXXXX
VER:8
at:XXXXXXXXXXXXXX
it:30
SID:XXXXXXXXXXXX
RID:XXXXXXXXX
AID:XXXXXXXXXX
zx:XXXXXXXXXXXX
t:1
Request Payload
count=1&ofs=211&req0_type=cf&req0_focused=1&req0__sc=c
Response Headers
cache-control:no-cache, no-store, max-age=0, must-revalidate
content-encoding:gzip
content-type:text/plain; charset=utf-8
date:Tue, 09 Oct 2012 08:52:46 GMT
expires:Fri, 01 Jan 1990 00:00:00 GMT
pragma:no-cache
server:GSE
status:200 OK
version:HTTP/1.1
x-content-type-options:nosniff
x-xss-protection:1; mode=block

Categories

Resources