I am writing a hapi js plugin, and was wondering what's the difference between the two ways of exposing methods that other plugins can use.
Method 1:
server.method("doSomething",
function () {
// Something
});
Method 2:
server.app.doSomething = function () {
// Something
};
In the first approach, the function can later be called as server.doSomething(), while using the second approach as server.app.doSomething().
So why would I use one way instead of another?
Looking at the API docs it sounds like they intended server.methods to be used for functions and server.app to be used for app settings/configuration. My guess is you should stick with server.method if you want to expose server level methods to be used in your plugins.
server.methods
An object providing access to the server methods where each server
method name is an object property.
var Hapi = require('hapi');
var server = new Hapi.Server();
server.method('add', function (a, b, next) {
return next(null, a + b);
});
server.methods.add(1, 2, function (err, result) {
// result === 3
});
server.app
Provides a safe place to store server-specific run-time application
data without potential conflicts with the framework internals. The
data can be accessed whenever the server is accessible. Initialized
with an empty object.
var Hapi = require('hapi');
server = new Hapi.Server();
server.app.key = 'value';
var handler = function (request, reply) {
return reply(request.server.app.key);
};
Related
I've built a simple smart contract to run on the Ethereum blockchain and I'm trying to replicate some of it's behavior on Solana. After making some slight changes I've managed to compile the program with Solang targeting Solana, but I'm not sure how to go about calling the methods; there doesn't seem to be a great wealth of documentation or examples on this. For example, if my program were written as follows:
contract Example {
function foo(...) { ... }
function bar(...) { ... }
}
How would I specify a call to foo vs a call to bar? Furthermore, how would I encode the arguments for these method calls?
Currently my approach is to use the #solana/buffer-layout library to encode my arguments as a struct, starting with lo.u8('instruction') to specify the method call (in the example case, I assume 0 would refer to foo and 1 would refer to bar). I've taken this approach based on looking at the source code for #solana/spl-token (specifically this file and it's dependencies) but I'm not sure if it will work for a program compiled using Solang, and the buffer layout encoding has been throwing an unexpected error as well:
TypeError: Blob.encode requires (length 32) Uint8Array as src
The code throwing this error is as follows:
const method = lo.struct([
lo.u8('instruction'),
lo.seq(
lo.blob(32),
lo.greedy(lo.blob(32).span),
'publicKeys',
),
])
const data = Buffer.alloc(64); // Using method.span here results in an error, as method.span == -1
method.encode(
{
instruction: 0,
publicKeys: [firstPublicKey, secondPublicKey],
},
data,
);
While this type error seems obvious, it doesn't line up with the sample code in the solana-labs/solana-program-library repository. I'm pretty sure this problem has to do with my use of lo.seq() but I'm not sure what the problem is.
Is my approach to this correct besides this type error, or is my approach fundamentally wrong? How can I call the intended method with encoded arguments? Thank you for any help.
There's a better library for you to use, #solana/solidity, which has a Contract class to encapsulate calls on the contract.
For example, in your case, you could do:
const { Connection, LAMPORTS_PER_SOL, Keypair } = require('#solana/web3.js');
const { Contract, Program } = require('#solana/solidity');
const { readFileSync } = require('fs');
const EXAMPLE_ABI = JSON.parse(readFileSync('./example.abi', 'utf8'));
const PROGRAM_SO = readFileSync('./example.so');
(async function () {
console.log('Connecting to your local Solana node ...');
const connection = new Connection('http://localhost:8899', 'confirmed');
const payer = Keypair.generate();
console.log('Airdropping SOL to a new wallet ...');
const signature = await connection.requestAirdrop(payer.publicKey, LAMPORTS_PER_SOL);
await connection.confirmTransaction(signature, 'confirmed');
const program = Keypair.generate();
const storage = Keypair.generate();
const contract = new Contract(connection, program.publicKey, storage.publicKey, EXAMPLE_ABI, payer);
await contract.load(program, PROGRAM_SO);
console.log('Program deployment finished, deploying the example contract ...');
await contract.deploy('example', [true], program, storage);
const res = await contract.functions.foo();
console.log('foo: ' + res.result);
const res2 = await contract.functions.bar();
console.log('bar: ' + res2.result);
})();
Example adapted from https://github.com/hyperledger-labs/solang#build-for-solana
More information about the package at https://www.npmjs.com/package/#solana/solidity
I have 3 different method responses in the API I'm working on currently set up like this:
app.use('/image', require('./routes/image/get'));
app.post('/image', require('./routes/image/post'));
app.put('/image', require('./routes/image/put'));
Is there a better way to do this?
You may use .route() on your app's Express object to reduce some of the redundancy in your route definitions.
app.route('/image')
.post(require('./routes/image/post'))
.put(require('./routes/image/put'));
There is also .all(), which will invoke your handler regardless of the request http method.
No use()
I've omitted .use(), mentioned above, because it is not available on Route objects -- it sets up application middleware. Routers are another layer of middleware (see this question for an explanation of the difference). If the intent is really to call .use(), and not .get(), then that line would have to stay, before the call to .route() (middleware registration order matters).
Reusing the same handler for different http methods
If one would prefer to reuse the same handler for a set of methods, in the following style:
app.route("/image").allOf(["post", "put"], function (req, res) {
//req.method can be used to alter handler behavior.
res.send("/image called with http method: " + req.method);
});
then, the desired functionality can be obtained by adding a new property to express.Route's prototype:
var express = require('express');
express.Route.prototype.allOf = function (methods /*, ... */) {
"use strict";
var i, varargs, methodFunc, route = this;
methods = (typeof methods === "string") ? [methods] : methods;
if (methods.length < 1) {
throw new Error("You must specify at least one method name.");
}
varargs = Array.prototype.slice.call(arguments, 1);
for (i = 0; i < methods.length; i++) {
methodFunc = route[methods[i]];
if (! (methodFunc instanceof Function)) {
throw new Error("Unrecognized method name: " +
methods[i]);
}
route = methodFunc.apply(route, varargs);
}
return route;
};
I have a mongoose (3.1) 'Thing' schema whose toJSON I can customize in the following manner...
Thing.options.toJSON = {};
Thing.options.toJSON.transform = function (doc, ret, options){
// do something to ret, depending on options
}
As noted in the code comment, I would like to change the JSON representation given the value of options. I would like to pass these options in an expressjs action, maybe...
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
var myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
How do I modify expressjs to allow me to supply options?
Thanks,
G
IMHO, the accepted answer (#VladStirbu's) is wrong because the options are being set at the schema level. It's changing the schema, so those options will be available in subsequent calls, even if you don't request so explicitly.
The options should be set inline, individually for that call:
Regular call using express:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
return response.send(things);
});
});
Call using express, but passing inline options to toJSON():
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
let toJSONOptions; // may be undefined, it's fine
if ( /* whatever condition you decide */ ) {
// this keeps the schema's original options:
toJSONOptions = Object.assign({ }, Thing.schema.options.toJSON);
// request to use original transform function, if any:
toJSONOptions.transform = true;
// set your own options to be passed to toJSON():
toJSONOptions._options = {...}; // whatever you need here
}
return response.send( things.map(e => e.toJSON(toJSONOptions)) );
});
});
No problem if toJSONOptions = undefined, it would be like a regular call to toJSON(), which is what express does when stringifying.
If you're using findOne() or findById(), then just return:
return response.send( thing.toJSON(toJSONOptions) );
This is the Mongoose commit that made me think of this:
https://github.com/Automattic/mongoose/commit/1161f79effc074944693b1799b87bb0223103220
You could pass options in the route handler by passing them to the schema options:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
Thing.schema.options.toJSON.myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
this way, the options will be available in the transform function as a property of the options object:
Thing.options.toJSON.transform = function (doc, ret, options){
console.log(options.myOptions); // prints the app specific data provided earlier
}
Is there a nice way to prevent duplicate routes from being registered in express? I have a pretty large application with hundreds of routes across different files, and it gets difficult to know if I've already registered a certain route when I go to add a new one. For example, I'd like to throw an error when express gets to routes487.js:
File: routes1.js
var ctrl = require('../controllers/testctrl');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi);
app.get('/there', auth.getToken, ctrl.there);
};
File: routes487.js
var ctrl = require('../controllers/testctrl487');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi487);
};
You could try a custom solution by wrapping express methods with the validation. Consider the following modification to your express app:
// route-validation.js
module.exports = function (app) {
var existingRoutes = {}
, originalMethods = [];
// Returns true if the route is already registered.
function routeExists(verb, path) {
return existingRoutes[verb] &&
existingRoutes[verb].indexOf(path) > -1;
}
function registerRoute(verb, path) {
if (!existingRoutes[verb]) existingRoutes[verb] = [];
existingRoutes[verb].push(path);
}
// Return a new app method that will check repeated routes.
function validatedMethod(verb) {
return function() {
// If the route exists, app.VERB will throw.
if (routeExists(verb, arguments[0]) {
throw new Error("Can't register duplicate handler for path", arguments[0]);
}
// Otherwise, the route is saved and the original express method is called.
registerRoute(verb, arguments[0]);
originalMethods[verb].apply(app, arguments);
}
}
['get', 'post', 'put', 'delete', 'all'].forEach(function (verb) {
// Save original methods for internal use.
originalMethods[verb] = app[verb];
// Replace by our own route-validator methods.
app[verb] = validatedMethod(verb);
});
};
You just need to pass your app to this function after creation and duplicate route checking will be implemented. Note that you might need other "verbs" (OPTIONS, HEAD).
If you don't want to mess with express' methods (we don't know whether or how express itself or middleware modules will use them), you can use an intermediate layer (i.e., you actually wrap your app object instead of modifying its methods). I actually feel that would be a better solution, but I feel lazy to type it right now :)
Is there a way to rename the query string parameter that holds the name of callback function? Say, I've got a legacy app which sources I can't access, I want it to be switched to ServiceStack, but the app uses "function" query string parameter, while SS expects "callback".
You can do it with a response filter, inside AppHost.Configure():
ResponseFilters.Add((req, res, dto) =>
{
var func = req.QueryString.Get("function");
if (!func.isNullOrEmpty())
{
res.AddHeader("Content-Type", ContentType.Html);
res.Write("<script type='text/javascript'>{0}({1});</script>"
.FormatWith(func, dto.ToJson()));
res.Close();
}
});