i'am asking the about the right way to downlaod a PushStreamContent present in Post Request ,
i already preapred the backend request , something like this
private static HttpClient Client { get; } = new HttpClient();
public HttpResponseMessage Get()
{
var filenamesAndUrls = new Dictionary<string, string>
{
{ 'README.md', 'https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/README.md' },
{ '.gitignore', 'https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/.gitignore'},
};
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new PushStreamContent(async (outputStream, httpContext, transportContext) =>
{
using (var zipStream = new ZipOutputStream(outputStream))
{
foreach (var kvp in filenamesAndUrls)
{
zipStream.PutNextEntry(kvp.Key);
using (var stream = await Client.GetStreamAsync(kvp.Value))
await stream.CopyToAsync(zipStream);
}
}
}),
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "MyZipfile.zip" };
return result;
}
and in the front part , i used axios to send Post request and with the result i create blob to download it (i modified the backend to support Post)
but the download take much time and i think this a wrong way to use PushStreamContent and i should use EventSource or something like this.
Thank you.
After few days of search , there are two solution :
Change the download request to Get Request instead of Post.
Use Fetch instead of axios http request and with the response send it to streamsaver package , it's really amazing and instantly start the download on the fly.
I agree with houssem about changing it to a get request.
I'm the creator of StreamSaver and occasionally i search for ppl talking about it and help someone in need. I often have to tell ppl that it's better to use the server to save files rather than using StreamSaver. StreamSaver is meant for client generated content (good for stuff like WebTorrent or webcam recording)
Download can only happen when you navigate the resource. That means you can't use ajax (xhr, fetch, axios, etc) to trigger a download
a <a href>, <iframe>, location.href = all works fine, but if you really need it to be a post request, then you can also submit a <form> but you will have to do it with application/multipart or URLEncoded and not with a json request or anything else. the con of this is that you can't use custom request headers like a authentication header (unless you use service worker to add them)
in such cases it is better with cookies that gets sent along every request
Related
I need to communicate with a soap:xml API from a node server on the Wix.com platform. The API requires Soap WSSE authentication.
I can send an authenticated request to the endpoint in SoapUI, however haven't been able successfully do this on the Wix node platform.
Wix only have a subset of node packages available for install and XMLHttpRequest is not available in their environment.
I have tried node-soap but receive errors which indicate the package might be buggy on the Wix node platform.
I've found myself using the node "request" (https://www.npmjs.com/package/request) package and trying to roll my own solution to work around missing node packages and environment restrictions.
Currently I can send a request to the end point however I receive the following response;
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Client</faultcode><faultstring>Access denied</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>\n
This suggests to me i'm not authenticating correctly.
As I mentioned, I've been able to successfully send requests and receive expected responses via SoapUI. So the API is functioning, and I suspect it's my implementation that is at fault. I'll be honest, I've worked with REST/JSON API's in the past, and it has been a long time since i've worked with a SOAP API, and I remember even back then having a whole lot of pain!
my request code
import request from 'request';
import {wsseHeaderAssoc} from 'backend/wsse';
export function getLocationID() {
let apiUsername = "username";
let apiPassword = "password";
let apiURL = "https://api.serviceprovider.com/wsdl";
// WSSE authentication header vars
let wsse = wsseHeaderAssoc(apiUsername, apiPassword);
let wsseUsername = wsse["Username"];
let wssePasswordDigest = wsse["PasswordDigest"];
let wsseCreated = wsse["Created"];
let wsseNonce = wsse["Nonce"];
let xml =
`<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:masked:api">`+
`<soapenv:Header>`+
`<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">`+
`<wsse:UsernameToken wsu:Id="UsernameToken-19834957983507345987345987345">`+
`<wsse:Username>${wsseUsername}</wsse:Username>`+
`<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">${wssePasswordDigest}</wsse:Password>`+
`<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">${wsseNonce}</wsse:Nonce>`+
`<wsu:Created>${wsseCreated}</wsu:Created>`+
`</wsse:UsernameToken>`+
`</wsse:Security>`+
`</soapenv:Header>`+
`<soapenv:Body>`+
...
`</soapenv:Body>`+
`</soapenv:Envelope>`
var options = {
url: apiURL,
method: 'POST',
body: xml,
headers: {
'Content-Type':'text/xml;charset=utf-8',
'Accept-Encoding': 'gzip,deflate',
'Content-Length':xml.length,
'SOAPAction':"https://api.serviceprovider.com/wsdl/service",
'User-Agent':"Apache-HttpClient/4.1.1 (java 1.5)",
'Connection':"Keep-Alive"
}
};
let callback = (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log('Raw result ', response);
// If you ever get this working, do some mad magic here
};
console.log('Error ', response);
};
}
I'm using wsse-js (https://github.com/vrruiz/wsse-js/blob/master/wsse.js) to generate the PasswordDigest, Created datetime stamp and Nonce as the node wsse package (https://www.npmjs.com/package/wsse) isn't available on Wix. I've read over the code and based on what i've read elsewhere this looks like a good implementation.
I made one small addition to return the generated details in an assoc array;
export function wsseHeaderAssoc(Username, Password) {
var w = wsse(Password);
var wsseAssoc = [];
wsseAssoc["Username"] = Username;
wsseAssoc["PasswordDigest"] = w[2];
wsseAssoc["Created"] = w[1];
wsseAssoc["Nonce"] = w[0];
return wsseAssoc;
}
As stated earlier i'm receiving a response of;
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Client</faultcode><faultstring>Access denied</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>\n
And i'm expecting a valid SOAP XML response.
I've used the raw xml structure and headers from SoapUI to construct this, everything looks fine, i really have no idea where i'm going wrong.
I would love any pointers anyone could throw my way - I've lost 2 days trying to brute force this, I need help.
You can use the WSSecurity method from the soap package. An example from their README:
var options = {
hasNonce: true,
actor: 'actor'
};
var wsSecurity = new soap.WSSecurity('username', 'password', options)
client.setSecurity(wsSecurity);
Summary:
IDE: Eclipse
Server: Java
Client: Html/Javascript
After sending data via ajax/jquery to a servlet and using JadeGateway to send those information to an agent, resulting data should go back to html/javascript.
I need a way to connect either a servlet or an agent with an existing and running html file (ofc without reloading the page).
Background:
I want to create a agent based game using babylon.js and jade/pug (let's call it jade).
During a game, information about game states should go to an agent, so that he could figure out what to do (e.g. create more units or attack). After this, the agent needs to send the information to the game, to call the appropriate function.
What I did so far:
I connected the game (html/javascript) with a servlet using ajax. There I used JadeGateway to send an ACLMessage to an agent.
I also used websockets to connect client/server but this method is too limited for what i need i think.
I now need a tool/method or tips to send information from an agent or the servlet. Both is possible, because the agent can send information back to the jadegateway agent/servlet.
I know how to use ajax to call a servlet from html and right back, but now the information is tranfered to some other classes, so that didn't work anymore.
In the babylon.js scene.registerBeforeRender function I do this:
$.ajax({
url: "AgentListenerServlet",
type: "POST",
data: {playerString: convertPlayerToJson(player1)},
success: function(data) {
alert(data.command)
},
error: function() {
alert("Listener Failed")
}
});
Sending message to an agent via jadegateway:
JadeGateway.execute(new CyclicBehaviour() {
public void action() {
final ACLMessage msgSend = new ACLMessage(ACLMessage.INFORM);
msgSend.setContent(message);
msgSend.addReceiver(new AID("First", AID.ISLOCALNAME));
myAgent.send(msgSend);
final ACLMessage msgReceive = myAgent.receive();
if(msgReceive != null && msgReceive.getContent() != null){
//used this as immediate answer for ajax success function, but maybe it can also be used to send the returning agent message?
Map <String, Object> map = new HashMap<String, Object>();
map.put("command", "information");
write(response, map);
} else {
block();
}
}
});
private void write(HttpServletResponse response, Map<String, Object> map) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new Gson().toJson(map));
}
agent code (simplified):
public void action() {
final ACLMessage msgSend = new ACLMessage(ACLMessage.INFORM); //sending message
final ACLMessage msgReceive = receive(); //receiving message
//Here i get information out of sent json objects from jadegateway
//Maybe here send handled information back to html?
} else {
block();
}
}
Maybe showing this codes is not necessary, because they are working and the problem is not there, but the template for asking a question requested these.
Expected and results:
So the problem is not, that it isn't working, but that I need to know how to continue.
I don't know what I expect, because i don't know the possibilities.
Maybe, what would help me, is to send a simple string from the agent or servlet that i can call with the alert function in the game.
Or maybe i should ask this: is it possible to get an answer in ajax (success function) after the data was sent to the servlet and passed to an agent?
Sorry for the huge spam. Hope I made my problem clear.
No need to reply, websockets is the solution after all.
I just figured out, that you can push data from server to client without a client request with
session.getBasicRemote().sendText(msgReceive.getContent());
The session is automatically generated and you can reveice it with websockets onopen method:
#OnOpen
public void onOpen(Session session){
this.session = session;
}
I have a C# dotnetcore console app running on an AWS instance, and I would like to add in communication between this and my playfab cloudscript.
I can communicate from the C# console to playfab, that was simple just using the playfab nuget package. However I'm having trouble going the other way.
I only ever want to send a few different simple messages, so im not looking for anything too complex. What I have done so far, is I added the following to the start of my console application:
var listener = new HttpListener();
listener.Prefixes.Add("http://+:80/");
listener.Start();
Writer.WriteBuffer("Listening...");
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
Writer.WriteBuffer("Context: " + context.ToString());
Writer.WriteBuffer("request: " + request.ToString());
Writer.WriteBuffer("response: " + response.ToString());
string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
// You must close the output stream.
output.Close();
listener.Stop();
Writer.WriteBuffer is a wrapper for writing Console.Write essentially, just formats stuff in a much better way for me. I see the "listening..." come up, so great its listening.
Now, I copied an example from playfab and just adapted it very slightly. cloudscript is in js, so here is what I am running from playfab:
var headers = {
"X-MyCustomHeader": "Some Value"
};
var body = {
input: args,
userId: currentPlayerId,
mode: "foobar"
};
var url = "http://11.111.111.1/";
var content = JSON.stringify(body);
var httpMethod = "post";
var contentType = "application/json";
// The pre-defined http object makes synchronous HTTP requests
var response = http.request(url, httpMethod, content, contentType, headers);
return { responseContent: response };
11.111.111.1 is where I put the IP address of the AWS instance (I've changed it for obvious reasons).
When running this, I get "httpRequestError": "Timeout".
When I check on AWS, I have nothing else printed out other than "Listening...", so it hasn't handled anything yet.
I'm not too sure where the problem lies to be honest.
I am new in the area of websocket.
I can connect to websocket server using JavaScript using this code:
var webSocket = new WebSocket(url);
But for my application, I need to connect to the same server using c#. The code I am using is:
ClientWebSocket webSocket = null;
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
3rd line of the code results following error:
"Server returned status code 200 when status code 101 was expected"
After little bit of survey, I realised that somehow server can't switch http protocol to websocket protocol during connection process.
Am I doing anything stupid in my C# code or there is something going wrong with the server. I don't have any access to the server, as the url I am using is a third party one .
Could you please give me any suggestion regarding the issue?
TL; DR:
Use ReceiveAsync() in loop until Close frame is received or CancellationToken is canceled. That's how you get your messages. Sending is straightworward, just SendAsync(). Do not use CloseAsync() before CloseOutputAsync() - because you want to stop your receiving loop first. Otherwise - either the CloseAsync() would hang, or if you use CancellationToken to quit ReceiveAsync() - the CloseAsync() would throw.
I learned a lot from https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html .
Full answer:
Use Dotnet client, here, have an example cut out from my real life code, that illustrate how the handshaking is made. The most important thing most people don't understand about how the thing operates is that there is no magic event when a message is received. You create it yourself. How?
You just perform ReceiveAsync() in a loop that ends, when a special Close frame is received. So when you want to disconnect you have to tell the server you close with CloseOutputAsync, so it would reply with a similar Close frame to your client, so it would be able to end receiving.
My code example illustrates only the most basic, outer transmission mechanism. So you send and receive raw binary messages. At this point you cannot tell the specific server response is related to the specific request you've sent. You have to match them yourself after coding / decoding messages. Use any serialization tool for that, but many crypto currency markets use Protocol Buffers from Google. The name says it all ;)
For matching any unique random data can be used. You need tokens, in C# I use Guid class for that.
Then I use request / response matching to make request work without dependency on events. The SendRequest() methods awaits until matching response arrives, or... the connection is closed. Very handy and allows to make way more readable code than in event-based approach. Of course you can still invoke events on messages received, just make sure they are not matched to any requests that require response.
Oh, and for waiting in my async method I use SemaphoreSlim. Each request puts its own semaphore in a special dictionary, when I get the response, I find the entry by the response token, release the semaphore, dispose it, remove from the dictionary. Seems complicated, but it's actually pretty simple.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace Example {
public class WsClient : IDisposable {
public int ReceiveBufferSize { get; set; } = 8192;
public async Task ConnectAsync(string url) {
if (WS != null) {
if (WS.State == WebSocketState.Open) return;
else WS.Dispose();
}
WS = new ClientWebSocket();
if (CTS != null) CTS.Dispose();
CTS = new CancellationTokenSource();
await WS.ConnectAsync(new Uri(url), CTS.Token);
await Task.Factory.StartNew(ReceiveLoop, CTS.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public async Task DisconnectAsync() {
if (WS is null) return;
// TODO: requests cleanup code, sub-protocol dependent.
if (WS.State == WebSocketState.Open) {
CTS.CancelAfter(TimeSpan.FromSeconds(2));
await WS.CloseOutputAsync(WebSocketCloseStatus.Empty, "", CancellationToken.None);
await WS.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
}
WS.Dispose();
WS = null;
CTS.Dispose();
CTS = null;
}
private async Task ReceiveLoop() {
var loopToken = CTS.Token;
MemoryStream outputStream = null;
WebSocketReceiveResult receiveResult = null;
var buffer = new byte[ReceiveBufferSize];
try {
while (!loopToken.IsCancellationRequested) {
outputStream = new MemoryStream(ReceiveBufferSize);
do {
receiveResult = await WS.ReceiveAsync(buffer, CTS.Token);
if (receiveResult.MessageType != WebSocketMessageType.Close)
outputStream.Write(buffer, 0, receiveResult.Count);
}
while (!receiveResult.EndOfMessage);
if (receiveResult.MessageType == WebSocketMessageType.Close) break;
outputStream.Position = 0;
ResponseReceived(outputStream);
}
}
catch (TaskCanceledException) { }
finally {
outputStream?.Dispose();
}
}
private async Task<ResponseType> SendMessageAsync<RequestType>(RequestType message) {
// TODO: handle serializing requests and deserializing responses, handle matching responses to the requests.
}
private void ResponseReceived(Stream inputStream) {
// TODO: handle deserializing responses and matching them to the requests.
// IMPORTANT: DON'T FORGET TO DISPOSE THE inputStream!
}
public void Dispose() => DisconnectAsync().Wait();
private ClientWebSocket WS;
private CancellationTokenSource CTS;
}
}
BTW, why use other libraries than the .NET built in? I can't find any reason other than maybe poor documentation of the Microsoft's classes. Maybe - if for some really weird reason you would want to use modern WebSocket transport with an ancient .NET Framework ;)
Oh, and I haven't tested the example. It's taken from the tested code, but all inner protocol parts were removed to leave only the transport part.
Since WebsocketSharp is not .NET Core compatible I suggest using websocket-client instead.
Here's some sample code
static async Task Main(string[] args)
{
var url = new Uri("wss://echo.websocket.org");
var exitEvent = new ManualResetEvent(false);
using (var client = new WebsocketClient(url))
{
client.MessageReceived.Subscribe(msg => Console.WriteLine($"Message: {msg}"));
await client.Start();
await client.Send("Echo");
exitEvent.WaitOne();
}
Console.ReadLine();
}
Be sure to use ManualResetEvent. Otherwise it doesn't work.
If you connect with a WebSocket client and you get an HTTP 200 as response, means that probably you are connecting to the wrong place (host, path and/or port).
Basically, you are connecting to a normal HTTP endpoint that is not understanding your WebSocket requirement, and it is just returning the "OK" response (HTTP 200). Probably the WebSocket server runs in another port or path in the same server.
Check your URL.
Not quite sure what happened to WebSocketSharp nuget package, however I noticed that now WebSocket# is showing up as most relevant result in nuget repo. It took me some time before I realized that Connect() is now returning Task, hopefully this example will be useful to someone:
using System;
using System.Threading.Tasks;
using WebSocketSharp;
namespace Example
{
class Program
{
private static void Main(string[] args)
{
using (var ws = new WebSocket(url: "ws://localhost:1337", onMessage: OnMessage, onError: OnError))
{
ws.Connect().Wait();
ws.Send("Hey, Server!").Wait();
Console.ReadKey(true);
}
}
private static Task OnError(ErrorEventArgs errorEventArgs)
{
Console.Write("Error: {0}, Exception: {1}", errorEventArgs.Message, errorEventArgs.Exception);
return Task.FromResult(0);
}
private static Task OnMessage(MessageEventArgs messageEventArgs)
{
Console.Write("Message received: {0}", messageEventArgs.Text.ReadToEnd());
return Task.FromResult(0);
}
}
}
All the libraries mentioned above are Wrappers. The .Net Frameworks class doing this is System.Net.WebSockets.ClientWebSocket
Websocket URLs should start with ws:// or wss:// where the latter is secure websocket.
Does anyone know if it is possible to send basic http authentication credentials with EventSource?
I'm looking for a solution to the same problem. This post here says this:
Another caveat is that as far as we know, you cannot change the HTTP
headers when using EventSource, which means you have to submit an
authorization query string param with the value that you would have
inserted using HTTP Basic Auth: a base64 encoded concatenation of your
login and a token.
Here is the code from the post:
// First, we create the event source object, using the right URL.
var url = "https://stream.superfeedr.com/?";
url += "&hub.mode=retrieve";
url += "&hub.topic=http%3A%2F%2Fpush-pub.appspot.com%2Ffeed";
url += "&authorization=anVsaWVuOjJkNTVjNDhjMDY5MmIzZWFkMjA4NDFiMGViZDVlYzM5";
var source = new EventSource(url);
// When the socket has been open, let's cleanup the UI.
source.onopen = function () {
var node = document.getElementById('sse-feed');
while (node.hasChildNodes()) {
node.removeChild(node.lastChild);
}
};
// Superfeedr will trigger 'notification' events, which corresponds
// exactly to the data sent to your subscription endpoint
// (webhook or XMPP JID), with a JSON payload by default.
source.addEventListener("notification", function(e) {
var notification = JSON.parse(e.data);
notification.items.sort(function(x, y) {
return x.published - y.published;
});
notification.items.forEach(function(i) {
var node = document.getElementById('sse-feed');
var item = document.createElement("li");
var t = document.createTextNode([new Date(i.published * 1000), i.title, i.content].join(' '));
item.appendChild(t);
node.insertBefore(item, node.firstChild);
// We add the element to the UI.
});
});
If your talk about cookies (not http auth):
EventSource uses http, so cookies are sent with the EventSource connection request.
Http auth should be supported as any other http url, although from the spec CORS+http auth is not supported.
Nowadays there is a NPM package to change the HTTP Header
https://www.npmjs.com/package/eventsource
This library is a pure JavaScript implementation of the EventSource
client. The API aims to be W3C compatible.
You can use it with Node.js or as a browser polyfill for browsers that
don't have native EventSource support.
You can use event-source-polyfill to add headers like this
import { EventSourcePolyfill } from 'event-source-polyfill';
new EventSourcePolyfill(`/api/liveUpdate`, {
headers: {
Authorization: `Bearer 12345`,
'x-csrf-token': `xxx-xxx-xxx`,
},
});
EventSource is about the server sending events to the client. I think you need bidirectional communication for authentication. How would you otherwise send the actual credentials?
WebSockets, however, can achieve that. Is that what you are looking for?
Update:
You can achieve what you want by utilizing cookies, as pointed out by 4esn0k. Cookies are sent along with the initial request that the browser makes to establish the connection. So, just make sure you set the session identifier for the cookie before launching any EventSource connections.