Handle POST data using Dart Route after already listening to stream - dart

I am using route to handle http requests to my server. This is my current route code:
HttpServer.bind("127.0.0.1", 8080).then((server) {
new Router(server)
..filter(new RegExp(r'/.*'), addCorsHeaders)
..filter(new RegExp(r'/admin/.*'), authenticate)
..serve(userGetURL, method: 'GET').listen(userGetHandler)
..serve(userPostURL, method: 'POST').listen(userPostHandler);
});
I am trying to get JSON data that I am POSTing to a URL. The data will be used to get an entity from the database and return it as JSON to the caller. I am basically trying to create a server application that will handle all the data and a client application that will display it.
I cannot figure out how to get the data from a POST. Everything I have tried requires that I listen to the stream, but it is already being listened to. This is how I have been trying to get the POST data:
userPostHandler(HttpRequest req) {
req.listen((List<int> buffer) {
// Return the data back to the client.
res.write(new String.fromCharCodes(buffer));
res.close();
}
}
The problem is I get a Bad state: Stream has already been listened to. error.
EDIT: The filters
Future<bool> authenticate(HttpRequest req) {
if (req.method == 'POST') {
// Post data is not null
// Authenticate user
String userName = '';
String password = '';
User user = new User();
user.DBConnect().then((User user) {
return new Future.value(user.ValidateUser(userName, password));
});
}
}
Future<bool> addCorsHeaders(HttpRequest req) {
print('${req.method}: ${req.uri.path}');
req.response.headers.add('Access-Control-Allow-Origin', '*, ');
req.response.headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS, GET');
req.response.headers.add('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept');
return new Future.value(true);
}

I have never used the Route package but I wonder why you want to listen inside the Handler. Can't you just access the properties you want to process?
Otherwise you could try
req.asBroadcastStream().listen(...)
A BroadcastStream supports multiple listeners.
More information in this article Use Streams for Data

Using the following code I was able to get a POST working:
void main() {
HttpServer.bind("127.0.0.1", 8080).then((server) {
new Router(server)
..filter(new RegExp(r'/.*'), addCorsHeaders)
..filter(new RegExp(r'/admin/.*'), authenticate)
..serve(userGetURL, method: 'GET').listen(userGetHandler)
..serve(userPostURL, method: 'POST').listen(userPostHandler);
});
}
Future userPostHandler(HttpRequest req) {
bool headerSent = false;
// Start listening before writing to the response.
req.listen((List<int> buffer) {
if (!headerSent) {
req.response.write("User POST");
headerSent = true;
}
req.response.write(new String.fromCharCodes(buffer));
},
// Use onDone to close the response.
onDone: () => req.response.close()
);
}
Here is what I figured out. Any write to the response automatically drains the body and thus destroy the POST data. As mentioned here. Also, listening to the response is done asynchronously and thus must be completed before close() is called.

Related

Manifest v3 extension: asynchronous event listener does not keep the service worker alive [duplicate]

I am trying to pass messages between content script and the extension
Here is what I have in content-script
chrome.runtime.sendMessage({type: "getUrls"}, function(response) {
console.log(response)
});
And in the background script I have
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.type == "getUrls"){
getUrls(request, sender, sendResponse)
}
});
function getUrls(request, sender, sendResponse){
var resp = sendResponse;
$.ajax({
url: "http://localhost:3000/urls",
method: 'GET',
success: function(d){
resp({urls: d})
}
});
}
Now if I send the response before the ajax call in the getUrls function, the response is sent successfully, but in the success method of the ajax call when I send the response it doesn't send it, when I go into debugging I can see that the port is null inside the code for sendResponse function.
From the documentation for chrome.runtime.onMessage.addListener:
This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).
So you just need to add return true; after the call to getUrls to indicate that you'll call the response function asynchronously.
The accepted answer is correct, I just wanted to add sample code that simplifies this.
The problem is that the API (in my view) is not well designed because it forces us developers to know if a particular message will be handled async or not. If you handle many different messages this becomes an impossible task because you never know if deep down some function a passed-in sendResponse will be called async or not.
Consider this:
chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {
if (request.method == "method1") {
handleMethod1(sendResponse);
}
How can I know if deep down handleMethod1 the call will be async or not? How can someone that modifies handleMethod1 knows that it will break a caller by introducing something async?
My solution is this:
chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {
var responseStatus = { bCalled: false };
function sendResponse(obj) { //dummy wrapper to deal with exceptions and detect async
try {
sendResponseParam(obj);
} catch (e) {
//error handling
}
responseStatus.bCalled= true;
}
if (request.method == "method1") {
handleMethod1(sendResponse);
}
else if (request.method == "method2") {
handleMethod2(sendResponse);
}
...
if (!responseStatus.bCalled) { //if its set, the call wasn't async, else it is.
return true;
}
});
This automatically handles the return value, regardless of how you choose to handle the message. Note that this assumes that you never forget to call the response function. Also note that chromium could have automated this for us, I don't see why they didn't.
You can use my library https://github.com/lawlietmester/webextension to make this work in both Chrome and FF with Firefox way without callbacks.
Your code will look like:
Browser.runtime.onMessage.addListener( request => new Promise( resolve => {
if( !request || typeof request !== 'object' || request.type !== "getUrls" ) return;
$.ajax({
'url': "http://localhost:3000/urls",
'method': 'GET'
}).then( urls => { resolve({ urls }); });
}) );

Empty request body in onFetch method of serviceworker

I am trying to make my website work offline, and while offline any POST request should be saved to a local database. Currently I have this code in my serviceworker to catch any requests:
self.onfetch = (event) => {
switch (event.request.method) {
case 'GET':
return onGet(event);
case 'POST':
return onPost(event);
}
};
async function onPost(event) {
if (navigator.onLine){
return;
}
event.request.clone().formData().then((data) => {
console.log(data);
})
}
I am trying to read out the data from the request, but the console.log returns an empty object. When looking at the failed request in DevTools the FormData is available. Is it not possible to read out the body when intercepting the request?

Pass custom data to service worker sync?

I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".

vibed: How can I handle POST request?

Could anybody help me to handle POST request, I read docs, but it's not clear to me, how to handle POST request, that I send from page, to vibed server.
I wrote next code:
import vibe.d;
import std.stdio;
void main()
{
auto router = new URLRouter;
router.any("*", &accControl);
router.any("/my", &action);
auto settings = new HTTPServerSettings;
settings.port = 8080;
settings.bindAddresses = ["::", "127.0.0.1"];
listenHTTP(settings, router);
runEventLoop();
}
void accControl(HTTPServerRequest req, HTTPServerResponse res)
{
res.headers["Access-Control-Allow-Origin"] = "*";
}
void action(HTTPServerRequest req, HTTPServerResponse res)
{
// how get string from POST request here. And how get JSON object, if server send it.
}
but what method I should use for req? As I understand expect POST body there is sending a lot of other data.
The POST request is sending with JQuery:
$.post("http://127.0.0.1:8080", "\"answers_result\":777");
So I need to get this JSON and send with vibed it's to DB. But problem that I can't understand how to handle it.
In main:
auto router = new URLRouter;
router.post("/url_to_match", &action);
listenHTTP(settings, router);
Action:
void action(HTTPServerRequest req, HTTPServerResponse res)
{
auto answers_result = req.json["answers_result"].to!int;
// ...
}
Or you can use registerRestInterface.
Here is an example code to show how to read POST params from vibe.d:
Main Function:
shared static this()
{
auto router = new URLRouter;
router.post("/url_to_match", &action);
auto settings = new HTTPServerSettings;
settings.port = 3000;
listenHTTP(settings, router);
}
Action:
void action(HTTPServerRequest req, HTTPServerResponse res)
{
// Read first POST parameter named "first_name"
auto firstName = req.form["first_name"];
// Read second POST parameter named "last_name"
auto lastName = req.form["last_name"];
// Prepare output to be sent to client.
auto name = "Hello %s, %s".format(lastName, firstName);
// Send data back to client
res.writeBody(name);
}
Build the program and run it, to try it out on your local machine you may execute the following simple curl request:
curl --data "first_name=kareem&last_name=smith" "http://localhost:3000/url_to_match"
HTH

Backbone.js: POST request with empty value

I am trying to make a POST request.
Here my code:
var myModel = new MydModel({
content: "ciao"
});
console.log(myModel.get("content")); // "ciao"
myModel.save();
If I look to the network activity it looks like this:
The response part {id:0, content:"", ……}
In the header part: Request Payload {"content":"ciao"}
Here my model:
define([], function () {
var MyModel = Backbone.Model.extend({
url: function url ()
{
return "http://localhost/users";
}
});
return MyModel;
});
Is it my problem or is it in the server part?
send/receive vs request/response
a server receives requests and sends responses
a client sends requests and receives responses
in short
if {id:0, content:"", ……} (the response) is wrong, it's your server
if {"content":"asdasdsa"} (the request) is wrong, it's your client
There is little problem with receiving JSON-payload that "Backbone-client" sends to your Apache-server.
All you need to do is to manually parse JSON-payload from input on the server side ("php://input", for PHP), like this:
if($_SERVER['REQUEST_METHOD'] == 'PUT' || $_SERVER['REQUEST_METHOD'] == 'POST') {
$postStr = file_get_contents("php://input");
//json_decode throws error or returns null if input is invalid json
try {
$json = json_decode($postStr, true);
if(empty($json)) {
throw new Exception("Not valid json");
}
//must not be json, try query str instead
} catch(Errfor $e) {
$postVars = parse_str($postStr);
foreach($postVars as $key=>$data) {
$_POST[$key] = $data;
}
}
}
Full explanation you can find here:
http://colinbookman.com/2014/04/08/php-puts-posts-and-backbone-js/

Resources