using https.get instead of got causes a 308 - https

this is a difficult question to ask because I am mystified, but let's see…
I am comparing Got with https.get, and have the following, bare simple code that works. Both Got and https.get return exactly the same result.
But when I use exactly the same code in my Fastify application, Got works as expected but https.get results in a 308.
Is there some way I can debug this code to see what is being sent out by https.get that is causing the remote server to respond with a 308 instead of 200?
import got from 'got';
import https from 'https';
const withGot = async (uri) => {
try {
const json = JSON.parse((await got(uri)).body);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const withHttps = async (uri) => {
try {
const json = await getRequest(uri);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const getRequest = async (uri) => {
return new Promise((resolve) => {
https.get(uri, (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
/**
* Any 2xx status code signals a successful response but
* here we're only checking for 200.
**/
if (statusCode !== 200) {
error = new Error(`ERROR\n${'-'.repeat(50)}\nRequest Failed.\nURI: ${uri}\nStatus Code: ${statusCode}`);
}
else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\nExpected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
/**
* Consume response data to free up memory
**/
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
}
catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
});
}
const uri = 'https://zenodo.org/api/records/?q=phylogeny';
withGot(uri);
withHttps(uri);

I figured out the reason for the problem (and the solution)… seems like when I use https.get, I still have to pass the options with a port 443 (the default port for https), otherwise, https seems to knock on port 80 and then gets redirected to port 443 which results in the server sending back html which causes the JSON parser to croak. If I pass an options object like below, then it works. But, it is still weird that the standalone script works fine without the options, so I continue to be mystified even though I have found a solution.
const options = {
hostname: 'zenodo.org',
port: 443,
path: `/api/records/?${qs}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};

Related

`next.js` api is resolved before promise fullfill?

I want to achieve something like this:
call my website url https://mywebsite/api/something
then my next.js website api will call external api
get external api data
update external api data to mongodb database one by one
then return respose it's status.
Below code is working correctly correctly. data is updating on mongodb but when I request to my api url it respond me very quickly then it updates data in database.
But I want to first update data in database and then respond me
No matter how much time its take.
Below is my code
export default async function handler(req, res) {
async function updateServer(){
return new Promise(async function(resolve, reject){
const statusArray = [];
const apiUrl = `https://example.com/api`;
const response = await fetch(apiUrl, {headers: { "Content-Type": "application/json" }});
const newsResults = await response.json();
const articles = await newsResults["articles"];
for (let i = 0; i < articles.length; i++) {
const article = articles[i];
try {
insertionData["title"] = article["title"];
insertionData["description"] = article["description"];
MongoClient.connect(mongoUri, async function (error, db) {
if (error) throw error;
const articlesCollection = db.db("database").collection("collectionname");
const customQuery = { url: article["url"] };
const customUpdate = { $set: insertionData };
const customOptions = { upsert: true };
const status = await articlesCollection.updateOne(customQuery,customUpdate,customOptions);
statusArray.push(status);
db.close();
});
} catch (error) {console.log(error);}
}
if(statusArray){
console.log("success", statusArray.length);
resolve(statusArray);
} else {
console.log("error");
reject("reject because no statusArray");
}
});
}
updateServer().then(
function(statusArray){
return res.status(200).json({ "response": "success","statusArray":statusArray }).end();
}
).catch(
function(error){
return res.status(500).json({ "response": "error", }).end();
}
);
}
How to achieve that?
Any suggestions are always welcome!

Using a URL query parameter to version cached responses

I am trying to cache specific urls and each url has md5 hash and If the urls updated with new md5 i want to remove the current cache and add the new one.
cached url: http://www.mysite.lo/cards/index.php?md5=f51c2ef7795480ef2e0b1bd24c9e07
function shouldFetch(event) {
if ( event.request.url.indexOf( '/cards/') == -1 ) {
return false;
}
return true;
}
self.addEventListener('fetch', function(event) {
if (shouldFetch(event)) {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function (err) {
return caches.match(event.request);
});
}
})
);
}
});
I know we can use caches.delete() and so on, but I want to call it only if the md5 updated from the new request.
Thanks
You can accomplish roughly what you describe with the following, which makes use of the ignoreSearch option when calling cache.matchAll():
self.addEventListener('fetch', (event) => {
const CACHE_NAME = '...';
const url = new URL(event.request.url);
if (url.searchParams.has('md5')) {
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponses = await cache.matchAll(url.href, {
// https://developers.google.com/web/updates/2016/09/cache-query-options
ignoreSearch: true,
});
for (const cachedResponse of cachedResponses) {
// If we already have the incoming URL cached, return it.
if (cachedResponse.url === url.href) {
return cachedResponse;
}
// Otherwise, delete the out of date response.
await cache.delete(cachedResponse.url);
}
// If we've gotten this far, then there wasn't a cache match,
// and our old entries have been cleaned up.
const response = await fetch(event.request);
await cache.put(event.request, response.clone());
return response;
})());
}
// Logic for non-md5 use cases goes here.
});
(You could make things slightly more efficient by rearranging some of the cache-manipulation code to bring it out of the critical response path, but that's the basic idea.)

Cypress not waiting for Before block to complete

I am trying to achieve the following functionality
Before Block : Call the Cy.visit("/login") and call a Function which will trigger a REST API and process the REST API response and set the local storage.
Only after the local storage is set click on "My Account" Link
Here is the source Code I am trying.
import * as subscriberHelpers from '../../../helpers/subscriberHelpers';
import * as localStorage from '../../../helpers/localStorage';
describe('testCode', () => {
before((done) => {
cy.visit('/login', {
timeout: 10000,
onLoad: () => {
localStorage.write("CD-Environment", Cypress.env('defaultEnvironment'));
localStorage.write("CD-Language", "en-US");
localStorage.write("CD-SystemId", "85788485-e411-48a9-b478-610c1285dc1a");
}
})
subscriberHelpers.createSubscriber().then(()=>{
done();
})
})
it('sClick on my account link', () => {
cy.get('.c-header-listItem > .c-link').contains("My Account").click();
})
})
Here is the code to createSubscriber function
export function createSubscriber() {
let URL = `SOME URL`;
let body = {
Some Body
}
return new Promise((resolve, reject) => {
request.subscriberServiceRequest(URL, body).then((response) => {
if (response.status === 200 && ("SessionId" in response.body)) {
localStorage.write("CD-SessionId", response.body.SessionId);
localStorage.write("CD-SubscriberId", response.body.Subscriber.Id);
resolve();
}
else if (response.status === 200 && ("Fault" in response.body)) {
reject(response.body.Fault.Message);
}
})
})
}
Here is the code to subscriber Service request function
export function subscriberServiceRequest(url, body, headers = null) {
let defaultHeaders = { "CD-SystemId": "85788485-e411-48a9-b478-610c1285dc1a" }
if (headers != null) {
defaultHeaders = addHeaders(defaultHeaders, headers);
}
return new Cypress.Promise((resolve, reject) => {
cy.request({
url: url,
method: 'POST',
body: body,
headers: defaultHeaders
}).then((response) => {
resolve(response);
});
})
}
When I try Executing the code I am getting following error in cypress
But the element existing in the UI
Questions:
Why I am getting the error
How to call more than one async functions
in before block
How to tell cypress to wait till the functions on
before block get processed meaning not only wait till receiving the
response but wait till the response got processed in the THEN block
To answer your first question:
Why I am getting the error
.contains() specifically searches for elements within but not including the tag it is called on. In other words, someElement.contains("My Account") will not match someElement.
What you should have instead is this:
cy.get('.c-header-listItem').contains("My Account").click();
Or simply:
cy.contains("My Account").click();

Receiving Flux SSE in Angular 5

I have succesfully implemented this mechanism in my application:
https://vividcode.io/Spring-5-WebFlux-with-Server-Sent-Events/
I can receive events with curl every second, as shown in the example.
My problem is: I cannot receive these events in Angular 5. I have tried many things. Currently my service code looks like this:
public getMigrationProgress(processName: string): Observable<any> {
let headers: HttpHeaders = new HttpHeaders();
headers = headers.append('X-Authorization', this._sessionService.getAuthToken());
headers = headers.append('accept', 'text/event-stream');
let url = config.restApi.url + this.getResource() + '/' + processName;
return Observable.create(observer => {
let eventSource = new EventSourcePolyfill(url, { headers: headers });
eventSource.onmessage = (event => {
observer.next(event);
this.zone.run(() => {
console.log('prpprpr');
});
});
eventSource.onopen = (event) => {
observer.next(event);
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
};
});
}
It only opens connection, does not receive events (Method onopen works once, onmessage - never). Server sends them though.
Any ideas how to fix this?
Turned out that if you set event name on server, you cannot receive it by onmessage method.
In the example the event name was set to "random". In order to receive it you have to do it like this:
eventSource.addEventListener('random', function (event) {
console.log(event);
});

redux saga ajax call - not working as expected

I have this redux saga code where everything works okay...until the promise, after that things start to go wrong
here's the relevant code
const firstApiRequest = ()=>{
return $.ajax({
url: myUrl,// ,
type:'POST',
headers: {
"Accept":"application/json",
"Content-Type":"application/json",
},
data:JSON.stringify(bodyData),
success:function(res){
console.log(res);
return res;
}
})
};
export function *startCheckout() {
try {
yield put(showLoading());
const data = yield call(firstApiRequest);//const data ends
yield put({type:FIRST_REQUEST_DONE,payload:data});
} catch (err) {
yield put(firstRequestFail(err));
}
}
export function *checkout() {
yield takeEvery(SEND_FIRST_REQUEST, startCheckout);
}
The problem is that after the return res in firstApiRequest , I wanted to use the data to send the FIRST_REQUEST_DONE action , but what happens is that the flow goes to FIRST_REQUEST_FAIL and shows error as true.
The problem is that the api response is coming back successfully and I am getting the data inside the error when the flow goes to FIRST_REQUEST_FAIL part of reducer and data shows up as error.
here's the code for reducer
where flow goes to
case 'FIRST_REQUEST_FAIL':
return {
loading: false,
error: true,
errorMessage: action.err,
};
instead of going to
case 'FIRST_REQUEST_DONE':
return {
id: action.id,
};
so, what's wrong with the code here? why does it show error even after a succesful response from server?
You shouldn't be defining the success in your api request.
$.ajax will return a promise on its own:
const firstApiRequest = () => (
$.ajax({
url: myUrl,// ,
type:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/json",
},
data:JSON.stringify(bodyData),
}));
Also, why are you using jQuery for making the API requests? I'd suggest using axios or fetch
Here is an approach to handle API request using redux-saga:
First create a request helper
import 'whatwg-fetch';
function parseJSON(response) {
return response.json ? response.json() : response;
}
/**
* Checks if a network request came back fine, and throws an error if
not
*
* #param {object} response A response from a network request
*
* #return {object|undefined} Returns either the response, or throws an
* error
*/
function checkStatus(response, checkToken = true) {
if (response.status >= 200 && response.status < 300) {
return response;
}
return parseJSON(response).then(responseFormatted => {
const error = new Error(response.statusText);
error.response = response;
error.response.payload = responseFormatted;
throw error;
});
}
/**
* Requests a URL, returning a promise
*
* #param {string} url The URL we want to request
* #param {object} [options] The options we want to pass to "fetch"
*
* #return {object} The response data
*/
export default function request(url, options = {}) {
// Set headers
if (!options.headers) {
options.headers = Object.assign({
'Content-Type': 'application/json',
}, options.headers, {});
}
// Stringify body object
if (options && options.body) {
options.body = JSON.stringify(options.body);
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
}
In your saga
import { call, fork, put, takeLatest } from 'redux-saga/effects';
import request from 'utils/request';
import { submitSuccess, submitError } from './actions'; // path
to your actions.
import { SUBMIT } from './constants'; // The event you're listening
export function* submitData(action) {
try {
const response = yield call(request, 'your_url', { method: 'POST', body: action.body });
yield put(submitSuccess(response));
} catch(err) {
yield put(submitError(response.payload.message);
}
}
export function* defaultSaga() {
yield fork(takeLatest, SUBMIT, submitData);
}
export default defaultSaga;
Reducer
const initialState = fromJS({
submitSuccess: false,
submitReponse: '',
errorMessage: '',
});
function fooReducer(state = initialState, action) {
switch (action.type) {
case SUBMIT_SUCCESS:
return state
.update('submitSuccess', () => true)
.update('submitResponse', () => action.response);
case SUBMIT_ERROR:
return state.update('errorMessage', () => action.errorMessage);
//...
}
}
With this structure you should be able to catch your success and you error when you're making your request.

Resources