Cloudflare Workers - selectively cache HTML content - cloudflare-workers

I'm wanting to create a Cloudflare Worker which selectively caches HTML page contents equivalent to if I had a page rule for cache-level=cache everything, edge cache TTL=30 mins
Requests going via the below, simplified, worker code never hit the cache, instead making a request from my origin every time.
Any idea what I'm missing here?
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url)
if (request.method == "GET" && url.pathname == "/foo/bar") {
newurl=url.protocol + "//" + url.hostname + url.pathname + "?" + url.search
let response = await fetch(newurl, request, { cf: { cacheTtl: 1800 } })
response = new Response(response.body, response)
response.headers.delete("pragma")
return response
} else {
const response = await fetch(request)
return response
}
}

This call is the problem:
fetch(newurl, request, { cf: { cacheTtl: 1800 } })
fetch() takes two parameters, not three. JavaScript calling convention ignores extra parameters, so your { cf: { cacheTtl: 1800 } } is being ignored.
It looks like you aren't actually making changes to the URL, so perhaps you could simply do:
fetch(request, { cf: { cacheTtl: 1800 } })
If you really do want to rewrite the URL, then you'll need a two-step process:
request = new Request(newurl, request);
let response = await fetch(request, { cf: { cacheTtl: 1800 } });

Related

Blazor WASM(Hosted) with PWA : How to change the current service worker code to use Network First Strategy?

The Current code looks like does Cache first Strategy, How to modify it use Network first and than fallback to cache if network fails ?
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
//const shouldServeIndexHtml = event.request.mode === 'navigate';
console.log("onFetch : " + event.request.url.toLowerCase());
const shouldServeIndexHtml = event.request.mode === 'navigate';
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
if (event.request.url.indexOf('/api') != -1) {
try {
// Network first
var response = await fetch(event.request);
// Update or add cache
await cache.put(event.request, response.clone());
// Change return value
cachedResponse = response;
}
catch (e)
{
}
}
You can add something like this after:
cachedResponse = await cache.match(request);
This should always load api requests from the network first, since it's not part of the cache initially. Every time the cache is renewed for this request. If the request fails, the cached value will be used.

Cannot set headers after they are sent to the client with res.writeHead()

I am trying to redirect a user to the login if he isn't authenticated. I hardcoded the jwt for now. This works, but I only get an error saying Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
Since the function works I don't know what is wrong and couldn't really find an answer to it either. This is my code for reference:
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.statusCode = 302;
ctx.res.setHeader(302, location);
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So article, category and home pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
const jwt = false;
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx);
// Fetch global site settings from Strapi
const global = await fetchAPI("/global");
if (!jwt) {
if (ctx?.ctx.pathname === "/account") {
redirectUser(ctx.ctx, "/login");
}
}
// Pass the data to our page via props
return { ...appProps, pageProps: { global } };
};
Any help would be much appreciated.
The error "Error: Can't set headers after they are sent." means that you're already in the body, but some other function tried to set a header or statusCode. In your case it is the function ctx.res.setHeader(302, location); that's causing the issue.
After writeHead, the headers are baked in and you can only call res.write(body), and finally res.end(body).
You do not need to use setHeader when you are already using the writehead method.
Read here more about the writehead
So your redirectUser could be like :
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}

ClientException, and i can't print the returned value (the request body)

Alright i'm losing my mind here,
in my flutter app, i'm using this function to perform post requests :
Future<Map> postRequest(String serviceName, Map<String, dynamic> data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
http.Response response = await http.post(
_urlBase + '$_serverApi$serviceName',
body: jsonEncode(data),
);
if (response.statusCode == 200) {
responseBody = jsonDecode(response.body);
//
// If we receive a new token, let's save it
//
if (responseBody["status"] == "TOKEN") {
await _setMobileToken(responseBody["data"]);
// TODO: rerun the Post request
}
}
} catch (e) {
// An error was received
throw new Exception("POST ERROR");
}
return responseBody;
}
The problems are :
I get a ClientException (Not every time)
In another class, I stored the result of this function in a variable, it's supposed to return a Future<Map<dynamic, dynamic>>, when i printed it it shows :
I/flutter ( 9001): Instance of 'Future<Map<dynamic, dynamic>>'
But when i run the same post request directly (without using a function) it worked, and it shows the message that i was waiting for.
note: in both cases (function or not), in the server side it was the same thing.
this is the function where i used the post request:
void _confirm() {
if (_formKey.currentState.saveAndValidate()) {
print(_formKey.currentState.value);
var v = auth.postRequest("se_connecter", _formKey.currentState.value);
print(v);
} else {
print(_formKey.currentState.value);
print("validation failed");
}
}
Well for the second problem, i just did these changes:
void _confirm() async {
and
var v = await auth.postRequest('se_connecter', _formKey.currentState.value);
and yes it is stupid.
For the exception, it was the ssl encryption that caused it, so i removed it from my backend.

How to refresh data after refresh token refreshes jwt

I've been trying to get my refresh token to work for a while now, and I hope I'm close. My token refreshes and triggers a subsequent 200 call to whatever call caused the 401, but my the data on my page doesn't refresh.
When an access token expires, the following happens:
After the 401, the GetListofCompanyNames returns 200 with a list of names using the correct updated access token. However, my dropdown does not refresh.
My interceptor:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
var $http = $injector.get('$http');
return $http(rejection.config);
});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
My return statement on the 401 rejection looks suspect here, but I'm not sure what to replace it with. Thereby my question is: How can I get my page to refresh it's data when I make the new call?
Update:
This gets me past when the 200 returns and I can get a dropdown to refresh, but I lose any state on the page (ex. selected dropdown) with the below.
authService.refreshToken().then(function() {
var $state = $injector.get('$state');
$state.reload();
});
Back to the drawing board!
Try putting up your retry call in $timeout, it should work.
Here's the updated code:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
return $timeout(function() {
var $http = $injector.get('$http');
return $http(rejection.config);
}});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
$timeout returns a promise that is completed with what is returned
from the function parameter, so we can conveniently just return the
$http call wrapped in $timeout.
Thanks.
I think you may want to change up how you go about this. One way to go about this would be to inject the $rootScope into your authInterceptorService and then once you successfully refresh the token, call something like $rootScope.broadcast('tokenRefreshed').
I don't quite know how you have set up the view and controller that handles your dropdown, but I would set up a listener for that 'tokenRefreshed' event. From here, you can do another call to GetListofCompanyNames. If you do it this way you can easily control and ensure that the model gets updated.
My final solution:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
var $http;
var retryHttpRequest = function(config, deferred) {
$http = $http || $injector.get('$http');
$http(config).then(function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
});
}
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
authService.refreshToken().then(function() {
retryHttpRequest(rejection.config, deferred);
},
function () {
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
}
};
}
]);
Copied almost 1 for 1 from https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/services/authInterceptorService.js .
This one transparently handles all requests and refreshes them when necessary. It logs out users when the refresh token is expired and passes errors along to the controllers by properly rejecting them. However, it doesn't seem to work with multiple in flight requests, I'll look into that when I get a use case for it in my system.

How to handle http.get() when received no data in nodejs?

Let's say that I have a service that returns customer information by a given id. If it's not found I return null.
My app calls this services like this:
http.get('http://myserver/myservice/customer/123', function(res) {
res.on('data', function(d) {
callback(null, d);
});
}).on('error', function(e) {
callback(e);
});
Currently my service responds with 200 but no data.
How do I handle when no data is returned?
Should I change it to return a different http code? In this case how to handle this? I tried many different approaches without success.
First off, it's important to remember that res is a stream of data; as it stands, your code is likely to call callback multiple times with chunks of data. (The data event is fired each time new data comes in.) If your method has been working up to this point, it's only because the responses have been small enough that the response payload wasn't broken into multiple chunks.
To make your life easier, use a library that handles buffering HTTP response bodies and allows you to get the complete response. A quick search on npm reveals the request package.
var request = require('request');
request('http://server/customer/123', function (error, response, body) {
if (!error && response.statusCode == 200) {
callback(null, body);
} else {
callback(error || response.statusCode);
}
});
As far as your service goes, if a customer ID is not found, return a 404 – it's semantically invalid to return a 200 OK when in fact there is no such customer.
Use the Client Response 'end' event:
//...
res.on('data', function(d) {
callback(null, d);
}).on('end', function() {
console.log('RESPONSE COMPLETE!');
});
I finally found a way to solve my problem:
http.get('http://myserver/myservice/customer/123', function(res) {
var data = '';
response.on('data', function(d) {
data += d;
});
response.on('end', function() {
if (data != '') {
callback(null, data);
}
else {
callback('No customer was found');
}
});
}).on('error', function(e) {
callback(e);
});
This solves the problem, but I'm also going to adopt josh3736's suggestion and return a 404 on my service when the customer is not found.

Resources