How do I conditionally call a proxy server based on route params? - hapi.js

let post =
{
method: 'POST',
path: '/posts/new/{postType}',
handler: (request, reply) =>{
//when params is text
if (request.params.postType == "text")
{
return reply("successfully uploaded text post");
}
//when params is image
else (request.params.postType == "image")
{
//call proxy uri to save the image
/*proxy:
{
uri: proxyUri + '/post/image', //call proxy uri to save the image
passThrough: true,
acceptEncoding: false,
}*/
}
}
How do I conditionally call a proxy server based on route params so I use one rest api for post text, image, vides etc?

You can use reply.proxy().
const route = {
/* ... */
handler: (request, reply) => {
if (request.params.useproxy) {
return reply.proxy({host: 'example.com', port: 80, protocol: 'http'});
} else {
return reply('no proxy');
}
}
};

Related

Apollo Server & 4xx status codes

Currently, my Apollo Server(running on HapiJS) returns HTTP 200 for every request, including failed ones.
I would like the GraphQL server to return HTTP 4xx for unsuccessful requests. The primary reason for it is that I want to set up monitoring for my ELB.
I know that Apollo Server has an engine platform, but I want to implement it using my current infrastructure.
Any ideas of how I could accomplish that? I tried to capture 'onPreResponse' event for my HapiJS server but I couldn't modify status code there.
After reading this answer. Here is a solution by modifying the hapijs plugin graphqlHapi of hapiApollo.ts file.
server.ts:
import { makeExecutableSchema } from 'apollo-server';
import { ApolloServer, gql } from 'apollo-server-hapi';
import Hapi from 'hapi';
import { graphqlHapi } from './hapiApollo';
const typeDefs = gql`
type Query {
_: String
}
`;
const resolvers = {
Query: {
_: () => {
throw new Error('some error');
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const port = 3000;
async function StartServer() {
const app = new Hapi.Server({ port });
graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } });
app.ext('onPreResponse', (request: any, h: any) => {
const response = request.response;
if (!response.isBoom) {
return h.continue;
}
return h.response({ message: response.message }).code(400);
});
await app.start();
}
StartServer()
.then(() => {
console.log(`apollo server is listening on http://localhost:${port}/graphql`);
})
.catch((error) => console.log(error));
hapiApollo.ts:
import Boom from 'boom';
import { Server, Request, RouteOptions } from 'hapi';
import { GraphQLOptions, runHttpQuery, convertNodeHttpToRequest } from 'apollo-server-core';
import { ValueOrPromise } from 'apollo-server-types';
export interface IRegister {
(server: Server, options: any, next?: Function): void;
}
export interface IPlugin {
name: string;
version?: string;
register: IRegister;
}
export interface HapiOptionsFunction {
(request?: Request): ValueOrPromise<GraphQLOptions>;
}
export interface HapiPluginOptions {
path: string;
vhost?: string;
route?: RouteOptions;
graphqlOptions: GraphQLOptions | HapiOptionsFunction;
}
const graphqlHapi: IPlugin = {
name: 'graphql',
register: (server: Server, options: HapiPluginOptions, next?: Function) => {
if (!options || !options.graphqlOptions) {
throw new Error('Apollo Server requires options.');
}
server.route({
method: ['GET', 'POST'],
path: options.path || '/graphql',
vhost: options.vhost || undefined,
options: options.route || {},
handler: async (request, h) => {
try {
const { graphqlResponse, responseInit } = await runHttpQuery([request, h], {
method: request.method.toUpperCase(),
options: options.graphqlOptions,
query:
request.method === 'post'
? // TODO type payload as string or Record
(request.payload as any)
: request.query,
request: convertNodeHttpToRequest(request.raw.req),
});
// add our custom error handle logic
const graphqlResponseObj = JSON.parse(graphqlResponse);
if (graphqlResponseObj.errors && graphqlResponseObj.errors.length) {
throw new Error(graphqlResponseObj.errors[0].message);
}
const response = h.response(graphqlResponse);
Object.keys(responseInit.headers as any).forEach((key) =>
response.header(key, (responseInit.headers as any)[key]),
);
return response;
} catch (error) {
// handle our custom error
if (!error.name) {
throw Boom.badRequest(error.message);
}
if ('HttpQueryError' !== error.name) {
throw Boom.boomify(error);
}
if (true === error.isGraphQLError) {
const response = h.response(error.message);
response.code(error.statusCode);
response.type('application/json');
return response;
}
const err = new Boom(error.message, { statusCode: error.statusCode });
if (error.headers) {
Object.keys(error.headers).forEach((header) => {
err.output.headers[header] = error.headers[header];
});
}
// Boom hides the error when status code is 500
err.output.payload.message = error.message;
throw err;
}
},
});
if (next) {
next();
}
},
};
export { graphqlHapi };
Now, when the GraphQL resolver throws an error, the client-side will receive our custom response with Http status code 400 instead of 200 status code with GraphQL errors response.
General from the browser:
Request URL: http://localhost:3000/graphql
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
The response body is: {"message":"some error"}

Capture a Response from GET and Use it in the Next Request

I am trying to use the response of axios.get, and use it in axios.post. How can I use the response as a header in the POST request?
I tried using axios.post with headers defined in the request config:
var config = {
headers: {
'Access-Control-Allow-Origin': '*',
'user': newUser.eid,
'pass':'bd957c3fbb'
}
}
/*
const axios = require('axios')
getCrumb() {
return axios.get('https://jenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)', config)
.then(response => {
return response
})
}
*/
/* code to get jenkins crumb */
const getJenkinsCrumb = () => {
try {
return axios.get('https://jenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)', config)
.then((crumbValue) => {
console.log(crumbValue.data);
})
} catch (error) {
console.log(error)
}
}
getJenkinsCrumb();
I want use the response from the previous GET request (above) as a header in the POST call (below).
var crumbHeader = {
headers: {
'Access-Control-Allow-Origin': '*',
}
}
/* post api to kick off the build */
try {
return axios.post('https://abc123:bd95701859#jenkins.com/job/Non- PAR/job/Non-Prod-Jobs/job/uitest/job/TestJob/buildWithParameters?nodes=100000&clustername=clustername', crumbHeader)
.then((postKickTest) =>{
console.log(postKickTest.data);
})
} catch (error) {
console.log(error)
}
The Axios request config includes a headers property to specify the request's headers. The config can be specified as the 2nd argument of axios.post() (if using the two-argument signature) or the 3rd argument (if using the three-argument signature). This example demonstrates the two-argument signature of axios.post() that sets the headers with the dataresult of a previous request:
export default {
methods: {
async sendRequest() {
const userResp = await axios.get('https://reqres.in/api/users/2')
await axios.post('https://reqres.in/api/users', {
headers: userResp.data,
data: {
name: 'john doe',
job: 'leader',
}
})
},
}
}
demo
Side note: The Access-Control-Allow-Origin is a CORS header that can only be set by the server. It has no effect when sent from the client. It's possible you're incorrectly assuming that header is not reaching the server because it's not resolving a CORS issue.

Hapi.js reply.redirect() is not working after image upload

I have the following code, in my server. I'm uploading an image using mongoose and s3 and then want to redirect the user to another page but this isn't happening. (the upload is successful).
Routes.js:
{path: '/success', method: 'GET', config: controller.success} ......
controller.js:
imageUpload: {
payload: {
maxBytes: 209715200,
output: 'file',
parse: true
},
handler: function(request, reply) {
var userName = request.auth.credentials.username;
members.findMemberByUsername(userName, function(err, member){
if (err) {
return reply.view('upload', {error: err});
} else if (member) {
var IDImagePath = request.payload.uploadedIDname.path;
console.log(IDImagePath);
members.addID(member, IDImagePath, function(err1){
console.log("add id error", err1);
if (err1){
return reply.view('upload', {error: err1, member: member});
} else {
console.log("SUCCESSFUL!");
return reply.redirect('/success');
}
});
}
});
}
},
success: {
handler: function (request, reply){
request.auth.session.clear();
console.log("success handler working!!");
return reply.view('success');
}
}
The code hits both console.log("SUCCESSFUL") and console.log("success handler working!!") in the controller but the redirect doesn't take place. By the way I'm using 'Jade' as the templating language so I have a success.jade. Thanks.
I found out what the problem was. I'm using AJAX on the client side but didn't have a 'success' method to reload the page:
$('#submitID').click(function(){
var formData = new FormData($('#uploadID')[0]);
$.ajax({
url: '/api/image',
type: 'POST',
xhr: function() { // Custom XMLHttpRequest
var myXhr = $.ajaxSettings.xhr();
if(myXhr.upload){
console.log(myXhr.upload);
}
return myXhr;
},
success: function(data) {
window.location.href = "/success"
},
data: formData,
cache: false,
contentType: false,
processData: false
}, "json");
});
I needed window.location.href = "/success" to reload the page. Please note the jQuery Ajax SUCCESS method is different to my '/success' route, they just happen to be the same word.

Can I get to response headers in Loopback afterRemote hook?

I have a Loopback model on which I am logging requests to keen.io using the afterRemote hook. http://docs.strongloop.com/display/public/LB/Remote+hooks#Remotehooks-ctx.result
I am also using the response-time package to add the response time header to the response. https://github.com/expressjs/response-time
This is working fine, expect I cannot figure out how to get to the X-Response-Time header in the response in order to log it to keen.io.
Can I get to the response headers in any way below?
module.exports = function(Studio) {
var isStatic = true;
var isNotStatic = false;
Studio.disableRemoteMethod('deleteById', isStatic); // DELETE /Studios/{id}
Studio.disableRemoteMethod('create', isStatic); // POST /Studios
Studio.disableRemoteMethod('upsert', isStatic); // PUT /Studios
Studio.disableRemoteMethod('updateAll', isStatic); // POST /Studios/update
Studio.disableRemoteMethod('updateAttributes', isNotStatic); // PUT /Studios/{id}
Studio.disableRemoteMethod('__create__ListenNps', isNotStatic);
Studio.disableRemoteMethod('__delete__ListenNps', isNotStatic);
Studio.disableRemoteMethod('__destroyById__ListenNps', isNotStatic);
Studio.disableRemoteMethod('__updateById__ListenNps', isNotStatic);
Studio.afterRemote('*', function(ctx, affectedModelInstance, next) {
var Keen = require('keen-js');
var client = new Keen({
projectId: "myid",
writeKey: "mykey"
});
var queryEvent = {
ip: ctx.req.ip,
baseUrl: ctx.req.baseUrl,
url: ctx.req.url,
route: ctx.req.route,
query: ctx.req.query,
method: ctx.methodString,
// response: ctx.result.???, What can I do here to get to the response headers? Specifically X-Response-Time
keen: {
timestamp: new Date().toISOString()
}
};
client.addEvent("queries", queryEvent, function(err, res) {
if (err) {
console.log(err)
} else {
console.log(res)
}
});
next();
});
};
Try to use ctx.res.getHeader('X-Response-Time') method
or
listen the res.on('finish') event.

Invalidate session with custom authenticator

Using ember-cli 0.1.2 and ember-cli-simple-auth 0.7.0, I need to invalidate the session both on client and server. As explained here I need to do something similar to the authenticate method making an ajax request to the server and ensuring its success before emptying the session:
import Ember from 'ember';
import Base from "simple-auth/authenticators/base";
var CustomAuthenticator = Base.extend({
tokenEndpoint: 'http://127.0.0.1:3000/api/v1/auth/login',
restore: function(data) {
},
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ email: credentials.identification, password: credentials.password }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'DELETE'
}).then(function(response) {
resolve();
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
// invalidate: function() {
// var _this = this;
// return new Ember.RSVP.Promise(function(resolve) {
// Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
// resolve();
// });
// });
// }
});
export default {
name : 'authentication',
before : 'simple-auth',
initialize : function(container) {
container.register('authenticator:custom', CustomAuthenticator);
}
};
My logout API endpoint need the token (in the headers). How do I pass it? I read this but my authorizer seems ignoring it and I got a 401:
import Ember from 'ember';
import Base from 'simple-auth/authorizers/base';
var CustomAuthorizer = Base.extend({
authorize: function(jqXHR, requestOptions){
Ember.debug("AUTHORIZING!");
}
});
export default {
name : 'authorization',
before : 'simple-auth',
initialize : function(container) {
container.register('authorizer:custom', CustomAuthorizer);
}
};
My environment.js:
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'wishhhh',
environment: environment,
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
// TODO: disabled because of https://github.com/stefanpenner/ember-cli/issues/2174
ENV.contentSecurityPolicyHeader = 'Disabled-Content-Security-Policy'
ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
// crossOriginWhitelist: ['http://localhost:3000']
crossOriginWhitelist: ['*']
}
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.baseURL = '/';
ENV.locationType = 'auto';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
}
if (environment === 'production') {
}
return ENV;
};
The following is the Ember inspector output when, eventually, I try to logout:
Did you actually configure Ember Simple Auth to use your custom authorizer? In that case it should authorize the session invalidation request automatically.
Alternatively you could add the token in the authenticator's invalidate method which gets passed the session's contents.
Thanks to marcoow, I found out that it was actually a problem with every request not only the logout one. My authorizer never got called. Problem was environment setup of crossOriginWhitelist which, in order to work with my dev API, I had to set to ['http://127.0.0.1:3000']. Neither ['http://localhost:3000'] nor [*] worked.