I'm trying to create a Shared Access Signature client side in my Node app. The reason being that I do not want to stream files through my app. I want the user to be able to upload a file to my Azure Data Lake Gen2 Blob Storage container directly.
I have looked at all examples I can find, but they are all server side. So I tried to generate generateDataLakeSASQueryParameters and use them in the PUT request. The process looks like it works and I return it to the client.
Server side:
async getFileUploadUrl(path) {
const now = new Date().toUTCString();
const startsOn = new Date(now);
startsOn.setMinutes(startsOn.getMinutes() - 10); // Skip clock skew with server
const expiresOn = new Date(now);
expiresOn.setHours(expiresOn.getHours() + 1); // Expires in one hour
const sharedKeyCredential = new StorageSharedKeyCredential(this.storageAccountName, this.accountKey);
const sas = generateDataLakeSASQueryParameters({
fileSystemName: this.fileSystemClient.name,
ipRange: { start: "0.0.0.0", end: "255.255.255.255" },
expiresOn,
protocol: SASProtocol.HttpsAndHttp,
permissions: DataLakeSASPermissions.parse("c").toString(), // Read (r), Write (w), Delete (d), List (l), Add (a), Create (c), Update (u), Process (p)
resourceTypes: AccountSASResourceTypes.parse("o").toString(), // Service (s), Container (c), Object (o)
services: AccountSASServices.parse("b").toString(), // Blob (b), Table (t), Queue (q), File (f)
startsOn,
version: "2019-12-12"
},
sharedKeyCredential);
const encodedURI = encodeURI(path);
const filePath = `${this.fileSystemClient.url}/${encodedURI}`;
return {
url: filePath,
signature: sas.signature,
};
}
Client side:
const { url, signature } = serverResponse;
const file = [file takes from an input tag];
const request = new XMLHttpRequest();
request.open('PUT', url, true);
request.setRequestHeader("x-ms-date", new Date().toUTCString());
request.setRequestHeader("x-ms-version", '2019-12-12');
request.setRequestHeader("x-ms-blob-type", 'BlockBlob');
request.setRequestHeader("Authorization", `SharedKey [storageaccount]:${signature}`);
request.send(file);
And what I keep getting back is a 403 with the following error:
The MAC signature found in the HTTP request '[signature]' is not the
same as any computed signature. Server used following string to sign:
'PUT\n\n\n1762213\n\nimage/png\n\n\n\n\n\n\nx-ms-date:Thu, 24 Sep 2020
12:24:05 GMT\nx-ms-version:2019-12-12\n/[account name]/[container
name]/[folder name]/image.png'.
Obviously I removed the actual signature since I have gotten it to work server side, but it looks something like this: hGhg765+NIGjhgluhuUYG686dnH90HKYFytf6= (I made this up, but it looks as if it's in the correct format).
I have also tried to return the parsed query string and used in a PUT request, but then I get errors stating there is a required header missing, and I cannot figure out which one that should be. No Authorization for instance should be required.
The method generateDataLakeSASQueryParameters is used to create a service sas token. After doing that, we can call Azure Datalake Rest API with the sas token as the query paramater
For example
Create sas token with method generateDataLakeSASQueryParameters. When we call method generateDataLakeSASQueryParameters, we should define a DataLakeSASSignatureValues class : https://learn.microsoft.com/en-us/javascript/api/#azure/storage-file-datalake/datalakesassignaturevalues?view=azure-node-latest
const {
StorageSharedKeyCredential,
generateDataLakeSASQueryParameters,
DataLakeSASPermissions,
} = require("#azure/storage-file-datalake");
const accountName = "testadls05";
const accountKey ="";
const now = new Date().toUTCString();
const startsOn = new Date(now);
startsOn.setMinutes(startsOn.getMinutes() - 10); // Skip clock skew with server
const expiresOn = new Date(now);
expiresOn.setHours(expiresOn.getHours() + 1); // Expires in one hour
const fileSas = generateDataLakeSASQueryParameters(
{
fileSystemName: "test",
pathName: "test.jpg",
permissions: DataLakeSASPermissions.parse("racwd"),
startsOn: startsOn,
expiresOn: expiresOn,
},
new StorageSharedKeyCredential(accountName, accountKey)
).toString();
console.log(fileSas);
Test (create file)
PUT http:// https://{accountName}.{dnsSuffix}/{filesystem}/{path}
?{sas token you create in step1}
Headers:
Content-Type:image/jpeg
Content-Length:0
Currently my nodeJS code running on my local machine takes a JSON Object, creates a zip file locally of the JSON Data and then uploads that newly created zip file to a destination web-service. The JSON Object passed in as a parameter is the result of query a db and aggregating the results into 1 large JSON Object.
The problem I am facing is that I am unsure how to remove the need for the creation of the zip file locally prior to making the http post request.
/**
* Makes a https POST call to a specific destination endpoint to submit a form.
* #param {string} hostname - The hostname of the destination endpoint
* #param {string} path - The path on the destination endpoint.
* #param {Object} formData - the form of key-value pairs to submit.
* #param {Object} formHeaders - the headers that need to be attached to the http request.
* #param {Object} jsonData - a large JSON Object that needs to be compressed prior to sending.
* #param {Object} logger - a logging mechanism.
*/
function uploadDataToServer(hostname, path, formData, formHeaders, jsonData, logger){
var jsonData = {};// large amout of JSON Data HERE.
var hostname = ""; // destination serverless
var path = ""; //destination path
let url = 'https://'+hostname+path;
return new Promise((resolve, reject) => {
var zip = new JSZip();
zip.file("aggResults.json", JSON.stringify(jsonData));
const zipFile = zip
.generateNodeStream({type:'nodebuffer',streamFiles:true})
.pipe(fs.createWriteStream('test.zip'))
.on('finish', function () {
const readStream = fs.createReadStream('test.zip');
console.log("test.zip written.");
request.post(url, {
headers:formHeaders,
formData: {
uploadFile:readStream
},
}, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(body);
}
})
})
});
}
Okay. so im trying to achieve to get socket.io inside all my express routes.
a portion of my code:
var port = process.env.PORT || 3000; // set our port
var server = app.listen(port);
var io = require('socket.io')(server);
app.io = io;
exports.io = io;
then i call it as follows inside other file.
var app = require('../../server');
var io = app.io;
function hijack(user,boatid) {
console.log("????");
console.log(user);
app.io.sockets.emit("myevent",{ test: 22});
var userid = user._id;
console.log(user);
}
module.exports = {
hijack : hijack(app),
};
But, it seems like user parameter inside hijack function now is occupied by the app, and, iff i add an exstra parameter, it still dont know the user parameter, as im calling in the main file by the following:
var ships_model = require('./app/gamemodels/ship_model.js');
ships_model.hijack(req.user, req.body.id).then(function (result) {
res.json(result);
});
Please note: i tried to inject the IO like the following:
var ships_model = require('./app/gamemodels/ship_model.js')(io);
but that just produced errors.
another example:
Is it possible to make a socket emit call within some functions? im only intrested to emit data to the client side.
Or how pusher server sided is working, could that be done with socket too?
the client request is as follows server sided
var bankfactory = require(path.resolve('./modules/articles/server/factory/user_factory.js'));
app.post('/api/bank', function (req, res) {
bankfactory.bank_inn(req.user._id,amount).then( function (bankresult) {
res.json(bankresult);
});
});
bankfactory:
exports.bank_inn = bank_inn;
function bank_inn(playerid,amount) {
if (playerid == 1) {
} else {
// possible to make a emit call to the client here?
//emit("newevent,datahere)
}
}
Note two: I already looked into eventemiters, but with no results.
So, how can i achieve to call socket.emit inside my express routes?
Additional structure code:
main file:
var ships_model = require('./app/gamemodels/ship_model.js');
ships_model.createShipInterface(req.user._id).then(function (response) {
res.json(response);
});
ship_model file have the following structure:
module.exports = {
getShips: getShips(),
createShipInterface : createShipInterface,
allowedLocationsShips : allowedLocationsShips,
startMissionInterface : startMissionInterface,
deligateShipMovements: deligateShipMovements,
upgradeBoat : upgradeBoat,
deletedBoats: deletedBoats,
hijackSession : hijackSession,
boats_to_hijack : boats_to_hijack,
avaliable_boats : avaliable_boats,
createHijackSession : createHijackSession,
public_hijack : public_hijack,
joinHijackSession : joinHijackSession,
leavehijack : leavehijack,
sendMessageToMembers : sendMessageToMembers,
KickMember : KickMember,
togglePublic : togglePublic,
getHangar : getHangar,
hijack : hijack(app),
getHangarSession: getHangarSession,
updateUserLocation : updateUserLocation,
};
As per your comment
So, how can i achieve to call socket.emit inside my express routes?
In our project, what we have done is create a socket server and express server. Thus both express server(server-socket) and browser(client-socket) are clients of socket server.
So whenever express server want to send something to browser, it send data to socket server with the identifier of browser(socket-Id or other unique identifier of client socket) to which we want to send. Then socket server using the identifier send data to the particular browser.
My goal is to intercept every outgoing message from my server in a meteor project and add some stuff (meta datas and additional contents)
I have this simple code :
var http = Npm.require( 'http' ),
originalWrite = http.OutgoingMessage.prototype.write;
http.OutgoingMessage.prototype.write = function ( chunk, encoding ) {
console.log( this, arguments );
chunk = chunk.replace( 'some code', 'elements to add' );
originalWrite.call( this, chunk, encoding );
}
It works but I cannot find the url of the current call. This is a problem because I need to add different elements according to the called url.
(nota : I have a condition to make sure the request is an html file)
The full URL isn't directly available but host and path are through the request header Host and a property path on the OutgoingMessage object.
To obtain the full URL:
var url = this.getHeader('host') + this.path; //or this._headers.host;
--
var originalWrite = http.OutgoingMessage.prototype.write;
http.OutgoingMessage.prototype.write = function () {
var url = this.getHeader('host') + this.path;
//...
return originalWrite.apply(this, arguments);
};
The reason path isn't available in the header is because the request path is part of the Request-Line. The OutgoingMessage implementation first establishes a TCP connection to the host and then issues a request on the path.
GET /path HTTP/1.1
Host: hostname
I want to be able to set a single cookie, and read that single cookie with each request made to the nodejs server instance. Can it be done in a few lines of code, without the need to pull in a third party lib?
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
Just trying to take the above code directly from nodejs.org, and work a cookie into it.
There is no quick function access to getting/setting cookies, so I came up with the following hack:
const http = require('http');
function parseCookies (request) {
const list = {};
const cookieHeader = request.headers?.cookie;
if (!cookieHeader) return list;
cookieHeader.split(`;`).forEach(function(cookie) {
let [ name, ...rest] = cookie.split(`=`);
name = name?.trim();
if (!name) return;
const value = rest.join(`=`).trim();
if (!value) return;
list[name] = decodeURIComponent(value);
});
return list;
}
const server = http.createServer(function (request, response) {
// To Read a Cookie
const cookies = parseCookies(request);
// To Write a Cookie
response.writeHead(200, {
"Set-Cookie": `mycookie=test`,
"Content-Type": `text/plain`
});
response.end(`Hello World\n`);
}).listen(8124);
const {address, port} = server.address();
console.log(`Server running at http://${address}:${port}`);
This will store all cookies into the cookies object, and you need to set cookies when you write the head.
If you're using the express library, as many node.js developers do, there is an easier way. Check the Express.js documentation page for more information.
The parsing example above works but express gives you a nice function to take care of that:
app.use(express.cookieParser());
To set a cookie:
res.cookie('cookiename', 'cookievalue', { maxAge: 900000, httpOnly: true });
To clear the cookie:
res.clearCookie('cookiename');
RevNoah had the best answer with the suggestion of using Express's cookie parser. But, that answer is now 3 years old and is out of date.
Using Express, you can read a cookie as follows
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.get('/myapi', function(req, resp) {
console.log(req.cookies['Your-Cookie-Name-Here']);
})
And update your package.json with the following, substituting the appropriate relatively latest versions.
"dependencies": {
"express": "4.12.3",
"cookie-parser": "1.4.0"
},
More operations like setting and parsing cookies are described here
and here
As an enhancement to #Corey Hart's answer, I've rewritten the parseCookies() using:
RegExp.prototype.exec - use regex to parse "name=value" strings
Here's the working example:
let http = require('http');
function parseCookies(str) {
let rx = /([^;=\s]*)=([^;]*)/g;
let obj = { };
for ( let m ; m = rx.exec(str) ; )
obj[ m[1] ] = decodeURIComponent( m[2] );
return obj;
}
function stringifyCookies(cookies) {
return Object.entries( cookies )
.map( ([k,v]) => k + '=' + encodeURIComponent(v) )
.join( '; ');
}
http.createServer(function ( request, response ) {
let cookies = parseCookies( request.headers.cookie );
console.log( 'Input cookies: ', cookies );
cookies.search = 'google';
if ( cookies.counter )
cookies.counter++;
else
cookies.counter = 1;
console.log( 'Output cookies: ', cookies );
response.writeHead( 200, {
'Set-Cookie': stringifyCookies(cookies),
'Content-Type': 'text/plain'
} );
response.end('Hello World\n');
} ).listen(1234);
I also note that the OP uses the http module.
If the OP was using restify, he can make use of restify-cookies:
var CookieParser = require('restify-cookies');
var Restify = require('restify');
var server = Restify.createServer();
server.use(CookieParser.parse);
server.get('/', function(req, res, next){
var cookies = req.cookies; // Gets read-only cookies from the request
res.setCookie('my-new-cookie', 'Hi There'); // Adds a new cookie to the response
res.send(JSON.stringify(cookies));
});
server.listen(8080);
Let me repeat this part of question that answers here are ignoring:
Can it be done in a few lines of code, without the need to pull in a third party lib?
Reading Cookies
Cookies are read from requests with the Cookie header. They only include a name and value. Because of the way paths work, multiple cookies of the same name can be sent. In NodeJS, all Cookies in as one string as they are sent in the Cookie header. You split them with ;. Once you have a cookie, everything to the left of the equals (if present) is the name, and everything after is the value. Some browsers will accept a cookie with no equal sign and presume the name blank. Whitespaces do not count as part of the cookie. Values can also be wrapped in double quotes ("). Values can also contain =. For example, formula=5+3=8 is a valid cookie.
/**
* #param {string} [cookieString='']
* #return {[string,string][]} String Tuple
*/
function getEntriesFromCookie(cookieString = '') {
return cookieString.split(';').map((pair) => {
const indexOfEquals = pair.indexOf('=');
let name;
let value;
if (indexOfEquals === -1) {
name = '';
value = pair.trim();
} else {
name = pair.substr(0, indexOfEquals).trim();
value = pair.substr(indexOfEquals + 1).trim();
}
const firstQuote = value.indexOf('"');
const lastQuote = value.lastIndexOf('"');
if (firstQuote !== -1 && lastQuote !== -1) {
value = value.substring(firstQuote + 1, lastQuote);
}
return [name, value];
});
}
const cookieEntries = getEntriesFromCookie(request.headers.Cookie);
const object = Object.fromEntries(cookieEntries.slice().reverse());
If you're not expecting duplicated names, then you can convert to an object which makes things easier. Then you can access like object.myCookieName to get the value. If you are expecting duplicates, then you want to do iterate through cookieEntries. Browsers feed cookies in descending priority, so reversing ensures the highest priority cookie appears in the object. (The .slice() is to avoid mutation of the array.)
Settings Cookies
"Writing" cookies is done by using the Set-Cookie header in your response. The response.headers['Set-Cookie'] object is actually an array, so you'll be pushing to it. It accepts a string but has more values than just name and value. The hardest part is writing the string, but this can be done in one line.
/**
* #param {Object} options
* #param {string} [options.name='']
* #param {string} [options.value='']
* #param {Date} [options.expires]
* #param {number} [options.maxAge]
* #param {string} [options.domain]
* #param {string} [options.path]
* #param {boolean} [options.secure]
* #param {boolean} [options.httpOnly]
* #param {'Strict'|'Lax'|'None'} [options.sameSite]
* #return {string}
*/
function createSetCookie(options) {
return (`${options.name || ''}=${options.value || ''}`)
+ (options.expires != null ? `; Expires=${options.expires.toUTCString()}` : '')
+ (options.maxAge != null ? `; Max-Age=${options.maxAge}` : '')
+ (options.domain != null ? `; Domain=${options.domain}` : '')
+ (options.path != null ? `; Path=${options.path}` : '')
+ (options.secure ? '; Secure' : '')
+ (options.httpOnly ? '; HttpOnly' : '')
+ (options.sameSite != null ? `; SameSite=${options.sameSite}` : '');
}
const newCookie = createSetCookie({
name: 'cookieName',
value: 'cookieValue',
path:'/',
});
response.headers['Set-Cookie'].push(newCookie);
Remember you can set multiple cookies, because you can actually set multiple Set-Cookie headers in your request. That's why it's an array.
Note on external libraries:
If you decide to use the express, cookie-parser, or cookie, note they have defaults that are non-standard. Cookies parsed are always URI Decoded (percent-decoded). That means if you use a name or value that has any of the following characters: !#$%&'()*+/:<=>?#[]^`{|} they will be handled differently with those libraries. If you're setting cookies, they are encoded with %{HEX}. And if you're reading a cookie you have to decode them.
For example, while email=name#domain.com is a valid cookie, these libraries will encode it as email=name%40domain.com. Decoding can exhibit issues if you are using the % in your cookie. It'll get mangled. For example, your cookie that was: secretagentlevel=50%007and50%006 becomes secretagentlevel=507and506. That's an edge case, but something to note if switching libraries.
Also, on these libraries, cookies are set with a default path=/ which means they are sent on every url request to the host.
If you want to encode or decode these values yourself, you can use encodeURIComponent or decodeURIComponent, respectively.
References:
Cookie Syntax
Set-Cookie Syntax
Additional information:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
You can use the "cookies" npm module, which has a comprehensive set of features.
Documentation and examples at:
https://github.com/jed/cookies
To get a cookie splitter to work with cookies that have '=' in the cookie values:
var get_cookies = function(request) {
var cookies = {};
request.headers && request.headers.cookie.split(';').forEach(function(cookie) {
var parts = cookie.match(/(.*?)=(.*)$/)
cookies[ parts[1].trim() ] = (parts[2] || '').trim();
});
return cookies;
};
then to get an individual cookie:
get_cookies(request)['my_cookie']
Cookies are transfered through HTTP-Headers
You'll only have to parse the request-headers and put response-headers.
Here's a neat copy-n-paste patch for managing cookies in node. I'll do this in CoffeeScript, for the beauty.
http = require 'http'
http.IncomingMessage::getCookie = (name) ->
cookies = {}
this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
parts = cookie.split '='
cookies[parts[0].trim()] = (parts[1] || '').trim()
return
return cookies[name] || null
http.IncomingMessage::getCookies = ->
cookies = {}
this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
parts = cookie.split '='
cookies[parts[0].trim()] = (parts[1] || '').trim()
return
return cookies
http.OutgoingMessage::setCookie = (name, value, exdays, domain, path) ->
cookies = this.getHeader 'Set-Cookie'
if typeof cookies isnt 'object'
cookies = []
exdate = new Date()
exdate.setDate(exdate.getDate() + exdays);
cookieText = name+'='+value+';expires='+exdate.toUTCString()+';'
if domain
cookieText += 'domain='+domain+';'
if path
cookieText += 'path='+path+';'
cookies.push cookieText
this.setHeader 'Set-Cookie', cookies
return
Now you'll be able to handle cookies just as you'd expect:
server = http.createServer (request, response) ->
#get individually
cookieValue = request.getCookie 'testCookie'
console.log 'testCookie\'s value is '+cookieValue
#get altogether
allCookies = request.getCookies()
console.log allCookies
#set
response.setCookie 'newCookie', 'cookieValue', 30
response.end 'I luvs da cookies';
return
server.listen 8080
Using Some ES5/6 Sorcery & RegEx Magic
Here is an option to read the cookies and turn them into an object of Key, Value pairs for client side, could also use it server side.
Note: If there is a = in the value, no worries. If there is an = in the key, trouble in paradise.
More Notes: Some may argue readability so break it down as you like.
I Like Notes: Adding an error handler (try catch) wouldn't hurt.
const iLikeCookies = () => {
return Object.fromEntries(document.cookie.split('; ').map(v => v.split(/=(.+)/)));
}
const main = () => {
// Add Test Cookies
document.cookie = `name=Cookie Monster;expires=false;domain=localhost`
document.cookie = `likesCookies=yes=withARandomEquals;expires=false;domain=localhost`;
// Show the Objects
console.log(document.cookie)
console.log('The Object:', iLikeCookies())
// Get a value from key
console.log(`Username: ${iLikeCookies().name}`)
console.log(`Enjoys Cookies: ${iLikeCookies().likesCookies}`)
}
What is going on?
iLikeCookies() will split the cookies by ; (space after ;):
["name=Cookie Monster", "likesCookies=yes=withARandomEquals"]
Then we map that array and split by first occurrence of = using regex capturing parens:
[["name", "Cookie Monster"], ["likesCookies", "yes=withARandomEquals"]]
Then use our friend `Object.fromEntries to make this an object of key, val pairs.
Nooice.
If you don't care what's in the cookie and you just want to use it, try this clean approach using request (a popular node module):
var request = require('request');
var j = request.jar();
var request = request.defaults({jar:j});
request('http://www.google.com', function () {
request('http://images.google.com', function (error, response, body){
// this request will will have the cookie which first request received
// do stuff
});
});
var cookie = 'your_cookie';
var cookie_value;
var i = request.headers.indexOf(cookie+'=');
if (i != -1) {
var eq = i+cookie.length+1;
var end = request.headers.indexOf(';', eq);
cookie_value = request.headers.substring(eq, end == -1 ? undefined : end);
}
I wrote this simple function just pass
req.headers.cookie and cookie name
const getCookieByName =(cookies,name)=>{
const arrOfCookies = cookies.split(' ')
let yourCookie = null
arrOfCookies.forEach(element => {
if(element.includes(name)){
yourCookie = element.replace(name+'=','')
}
});
return yourCookie
}
I know that there are many answer to this question already, but here's a function made in native JS.
function parseCookies(cookieHeader) {
var cookies = {};
cookieHeader
.split(";")
.map(str => str.replace("=", "\u0000")
.split("\u0000"))
.forEach(x => cookies[x[0]] = x[1]);
return cookies;
}
It starts by taking in the document.cookie string. Every key-value pair is separated by a semicolon (;). Therefore the first step is to divide the string up each key-value pair.
After that, the function replaces the first instance of "=" with a random character that isn't in the rest of the string, for this function I decided to use the NULL character (\u0000). The key-value pair can now be split into just two pieces. The two pieces can now be combined into JSON.
You can use cookie lib to parse incoming multiple cookies, so that you won't have to worry about exceptions cases:
var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2');
// { foo: 'bar', equation: 'E=mc^2' }
To write a cookie you can do like this:
response.writeHead(200, {
"Set-Cookie": `mycookie=cookie`,
"Content-Type": `text/plain`
});
First one needs to create cookie (I have wrapped token inside cookie as an example) and then set it in response.To use the cookie in following way install cookieParser
app.use(cookieParser());
The browser will have it saved in its 'Resource' tab and will be used for every request thereafter taking the initial URL as base
var token = student.generateToken('authentication');
res.cookie('token', token, {
expires: new Date(Date.now() + 9999999),
httpOnly: false
}).status(200).send();
To get cookie from a request on the server side is easy too.You have to extract the cookie from request by calling 'cookie' property of the request object.
var token = req.cookies.token; // Retrieving Token stored in cookies