I'm trying to add caching to a $resource in Angular, but no matter what I try (so far), Angular always ends up performing a full requests and the server happily returns a full response.
Simplified, my $resource looks like this:
app.factory("Brands", ["$resource", "JsonCache", function($resource, JsonCache) {
return $resource("/api/car_brands", {
query: {
method: "GET",
isArray: true,
cache: JsonCache
}
});
}]);
The JsonCache is made like this:
app.factory('JsonCache', function ($cacheFactory) {
return $cacheFactory('json-cache');
});
I've also tried with cache: true but doesn't help.
I'm using Angular 1.3.2 and Angular-resource 1.3.2.
Server returns these headers:
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: application/json
Date: Wed, 03 Dec 2014 16:20:59 GMT
Keep-Alive: timeout=5, max=99
Server: Apache/2.4.9 (Win32) OpenSSL/1.0.1g PHP/5.5.11
Transfer-Encoding: chunked
Vary: Accept-Encoding
I see no cache-specific headers, but when I tell Angular to cache the things, I'd expect it to cache, no matter what. Also I have no way of modifying these headers, so I have to deal with it.
I can't find any other details in the documentation that tell me there's anything else I need to set up (such as, I dunno, TTL?) so I can only assume that it should "just work". But why isn't it? What did I miss?
Related
NOTE:
I've found a possibly related issue that warrants a new question here
This is a weird problem. I've been using angular over the course of 2 years and have never run into this problem.
I'm using angular v1.5.0. I'm making a post request like this:
$http({
method: "POST",
url: "/myurl",
data: {
file: myFile // This is just an object
}
});
Run-of-the-mill POST request right? Get this. I look in the console and the Network tab logs the request as a GET. Bizarre. So I've jiggered the code to work like this:
$http.post("/myurl", {file: myFile});
Same thing. After stepping through the $http service code I'm confident that the headers are being set properly. Has anyone else run into this problem?
Update
Taking germanio's advice, i've tried using the $resource service instead:
promise = $resource("/upload").save()
(this returns an error for another reason, it still executes the POST properly). I'm having the same problem: the request is logged as a GET in the console.
Here are the headers of the request when it gets to my server:
GET /myurl/ HTTP/1.1
Host: localhost:8001
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Pragma: no-cache
Referer: http://localhost:8001/myurl/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36
Update 2
As per georgeawg's suggestion I've used an interceptor to log the request at its various stages. Here is the interceptor code:
$httpProvider.interceptors.push(function() {
return {
request: function(config) {
console.log(config);
return config;
}
}
}
Having run this code I get this logged:
data:Object // contains file object
headers: Object // has Content-Type set to multipart
method:"POST" // ???
url :"/myurl
So this means the request is being sent as a POST from within Angular, but it is still logged as a GET both in the browser and on my server. I think there is something low level at work here about the HTTP protocol that I dont understand.
Is the request sent to the server before it is actually logged in the browser? If so, that might atleast point to my server as the culprit.
In the hopes of finding out whats going on, here is my server code:
type FormStruct struct {
Test string
}
func PHandler(w http.ResponseWriter, r *http.Request) {
var t FormStruct
req, _ := httputil.DumpRequest(r, true)
log.Println(string(req))
log.Println(r.Method) // GET
log.Println(r.Body)
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&t)
log.Println("Decoding complete")
if err != nil {
log.Println("Error")
panic(err.Error()+"\n\n")
}
log.Println(t.Test)
w.Write([]byte("Upload complete, no errors"))
}
func main() {
http.HandleFunc("/myurl/", PHandler)
fmt.Println("Go Server listening on port 8001")
http.ListenAndServe(":8001", nil)
}
My server throws an EOF error when it receives the request:
2016/03/30 10:51:37 http: panic serving [::1]:52039: EOF
Not sure what an EOF would even mean in this context.
Update 3
By the suggestion of another use, I tried using POSTMAN to hit my server with a fake POST request. The server receives the request properly. This means to me that there is something up with how angular is making the POST request. Please help.
Any ideas?
Full server logs:
Go Server listening on port 8001
2016/03/30 11:13:08 GET /myurl/ HTTP/1.1
Host: localhost:8001
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Postman-Token: 33d3de90-907e-4350-c703-6c57a4ce4ac0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36
X-Xsrf-Token: null
2016/03/30 11:13:08 GET
2016/03/30 11:13:08 {}
2016/03/30 11:13:08 Decoding complete
2016/03/30 11:13:08 Error
2016/03/30 11:13:08 http: panic serving [::1]:52228: EOF
goroutine 5 [running]:
net/http.(*conn).serve.func1(0xc820016180)
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1389 +0xc1
panic(0x3168c0, 0xc82000b1a0)
/usr/local/Cellar/go/1.6/libexec/src/runtime/panic.go:426 +0x4e9
routes.FUPHandler(0x1055870, 0xc820061ee0, 0xc820104000)
/Users/projectpath/routes.go:42 +0x695
net/http.HandlerFunc.ServeHTTP(0x4e7e20, 0x1055870, 0xc820061ee0, 0xc820104000)
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1618 +0x3a
net/http.(*ServeMux).ServeHTTP(0xc820014b40, 0x1055870, 0xc820061ee0, 0xc820104000)
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1910 +0x17d
net/http.serverHandler.ServeHTTP(0xc820016100, 0x1055870, 0xc820061ee0, 0xc820104000)
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:2081 +0x19e
net/http.(*conn).serve(0xc820016180)
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1472 +0xf2e
created by net/http.(*Server).Serve
/usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:2137 +0x44e
Update 4
I stumbled onto something interesting:
Charles logs a POST request when I post to myurl, but the response status is 301. After the POST a GET is logged. This is the GET that is hitting my server.
My server, as you can see above, does not do any sort of redirection. How is the 301 happening?
This is due to a security consideration.
In your situation when a redirect is sent back from the server to the browser, the browser will not repeat the POST request (but rather just a "simple" GET request).
Generally speaking a browser will not send POST data to a redirect URL because the browser is not qualified to decide if you're willing to send the same data to the new URL what you intended to send to the original URL (think about passwords, credit card numbers and other sensitive data). But don't try to circumvent it, simply use registered path of your handler to POST to, or any of the other tips mentioned in the linked answer.
For context see question:
Go web server is automatically redirecting POST requests
You can read more on the subject here:
Why doesn't HTTP have POST redirect?
This code actually send GET to server
$http({
method: 'POST',
params: {
LoginForm_Login: userData.username,
LoginForm_Password: userData.password
},
url: YOURURL
}).then(
You need to use transformRequest, sample below actually send POST
$http({
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
transformRequest: function (obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: {
LoginForm_Login: userData.username,
LoginForm_Password: userData.password
},
url: YOURURL
}).then(
I'm using angular to POST to an authentication endpoint; on the server side, I can see the request succeed, and proper CORS headers are set. Angular's origin is http://localhost:9000
On the server side, preflight OPTIONS requests always get a 200 back, so that seems OK.
On the client side, the $http.post always fails with an error code of 0, which from other research suggests something is still wrong with CORS. I've read the spec and tried a number of other answers, yet something is still missing.
Angular POSTs like this:
$http({
method: 'POST',
url: 'http://localhost:3000/auth/login',
data: {
username: $scope.username,
password: $scope.password
}
})
.then(function (response) {
/* etc. etc. */
}, function (response) {
/* This always triggers, with response.status = 0 */
console.log("ERROR: " + response.data);
console.log("Status: " + response.status);
console.log("Status text: " + response.statusText);
console.log("Headers: " + response.headers);
$scope.error = 'Something went wrong...';
});
Using curl to debug what the server is sending back, this is it:
< HTTP/1.1 302 Found
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
< Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization, Content-Length, X-Requested-With
< Access-Control-Allow-Credentials: true
< Set-Cookie: ua_session_token=(blahblah); Path=/
< Location: /
< Vary: Accept
< Content-Type: text/plain; charset=utf-8
< Content-Length: 23
< Date: Mon, 28 Dec 2015 15:08:17 GMT
< Connection: keep-alive
This is why I'm at a loss, as per the specification, the server seems to be doing the right thing?
Here's what the server gets from the client in terms of request headers:
HEADER host localhost:3000
HEADER content-type application/json;charset=UTF-8
HEADER origin http://localhost:9000
HEADER content-length 38
HEADER connection keep-alive
HEADER accept application/json, text/plain, */*
HEADER user-agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9
HEADER referer http://localhost:9000/
HEADER accept-language en-us
HEADER accept-encoding gzip, deflate
UPDATE tried something else with no luck, based on this post. It would seem Access-Control-Allow-Headers is case-sensitive, and angular is sending on the request accept, origin, content-type. I tweaked the server to parrot back the same, with no luck.
Alright, after applying my head to my keyboard for several hours, I've fixed it.
The answer seems to be that angular really doesn't like getting redirects in response to POST. When I changed the server endpoint to return just a plain auth token as text (the same token it was setting as a cookie anyway) rather than returning a redirect, the angular POST started working like a charm and falling through to the success handler.
Not sure I got deep enough into this to know why angular was behaving in that way; by playing around with it I found that if the redirect the server sent was to a nonexistent (404) URL that this could be replicated, EVEN IF the original POST returned that valid redirect.
I've got some Angular code where I am attempting to send a POST to a dev server.
var url = 'http://someDevUrl.com',
data = { 'someKey': 'some value' };
$http.post(url, data);
It sends the OPTIONS preflight request. I can see it hit the server, and the server gives it a happy response.
OPTIONS Response Headers:
HTTP/1.1 200 OK
Server: Cowboy
Date: Mon, 08 Dec 2014 18:20:44 GMT
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 30
Access-Control-Allow-Headers: x-requested-with, content-type, accept, origin, authorization, x-csrftoken
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Via: 1.1 vegur
but then Angular never sends the POST after that...
Typically when $http.post() only sends an OPTIONS request, that means the OPTIONS request returned an error (usually a CORS issue). But in this case, all is fine with the OPTIONS request & response, but it still won't send the POST.
Has anyone seen this before or have an idea of what might be preventing it from sending the POST?
EDIT:
I've got around the issue by adding a Content-Type: text/plain header to the request:
var url = 'http://someDevUrl.com',
data = { 'someKey': 'some value' },
config = {
headers: {
'Content-Type': 'text/plain'
}
}
};
$http.post(url, data, config);
which causes it to skip the OPTIONS preflight, thus avoiding the issue. I'm still curious to know why it was not working in the first place, since there was/is no CORS issue and the OPTIONS request was not sending back an error response.
I would like to get the 'X-Total-Count' response header of a RESTful API. While trying to get the header in the query callback function of my ngResource 'User', it seems that $http ignores a lot of the reponse headers.
These are the response headers of my request:
Access-Control-Allow-Origin: *
Cache-Control: max-age=0, private, must-revalidate
Connection: close
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Fri, 17 Oct 2014 11:13:26 GMT
Link: <http://xxxx.xxx/user?page=2>; rel="next"
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Total-Count: 32
Here I'm querying the set of users:
User.query({
page: $scope.pagingOptions.currentPage,
limit: $scope.pagingOptions.pageSize
}, function(users, responseHeaders) {
console.log(responseHeaders());
console.log(responseHeaders('X-Total-Count'));
});
That's the result of the console:
Object { cache-control="max-age=0, private, must-revalidate", content-type="application/json; charset=utf-8"}
null
So why the $http's responseHeaders() function turns only 2 of 10 header properties?
Are you sure you are not doing cross origin resource sharing?
see 7.1.1 Handling a Response to a Cross-Origin Request
That specification forbids access to any response header field other
except the simple response header fields (i.e. Cache-Control,
Content-Type, ...)
DEMO: http://jsfiddle.net/rob_balfre/7QUUf/
How do you POST data (across domain) using $resource?
For example this curl writes to the API with no problem:
curl --dump-header - -H "Content-Type: application/json" -X POST --data '{"name": "Wobbly"}' http://192.168.91.20/api/food/
and the header response is:
HTTP/1.0 201 CREATED
Date: Thu, 25 Oct 2012 08:19:42 GMT
Server: WSGIServer/0.1 Python/2.6.1
Access-Control-Allow-Headers: Content-Type,*
Location: http://localhost/api/food/15/
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
Content-Type: text/html; charset=utf-8
I'm totally stuck on how to get angular to POST the same way. This is my $resource, it's worth noting that 'get' works just fine:
angular.module('tastypieModule', ['ngResource']).
factory('FoodOptions', function($resource, $timeout) {
var FoodOptions = $resource('http://testurl/api/:type',
{type: 'food'},
{
get: {method: 'JSONP', params: {format: 'jsonp', callback:'JSON_CALLBACK'}},
update: {method: 'POST', headers: {'Content-Type': 'application/json'}}
}
);
return FoodOptions;
})
When I call update it just fails and I see this is the console network tab:
METHOD: OPTIONS
STATUS: (canceled) Load cancelled
On the server you need to implement a Cross Origin Resource Sharing. http://www.html5rocks.com/en/tutorials/cors/ and http://omarrr.com/cors-html5-approach-to-crossdomain-policies/ both have good articles about the topic. The HEAD request is coming from the browser to your server to check for the headers that contain (or in your case, don't contain) the CORS permissions. If you implement CORS on the server then you'll see the browser first make a HEAD request once to the server then after confirming the correct permissions it will make the POST.