In Swift, URLSession.shared.dataTask sometimes work and sometimes not - node.js

client: swift, xcode 8.3
server: nodejs, express, mongodb
In client side, it first send login request (username & password) to server, upon receive a valid token, it proceeds to the next screen to send another request to get a list of books.
The strange point is that, for the second request for books, sometimes it works - if with debug mode and a number of breakpoints proceeding "slowly", while sometimes it does not work - if proceed the breakpoints not such slow or without them. I repeated a number of times and figured out this pattern.
Here is the piece of code which send request - millions of thanks!!!
let url = URL(string: "http://" + SERVER_IP + ":" + PORT + "/books")
print("Query:>> url: ")
print(url)
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
print("Query>> response from loadBooks")
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) {
var books = [Book]()
if let array = json as? [Any] {
for i in 0...array.count-1 {
var bookJson = array[i] as? [String: Any]
let b = Book(json: bookJson!)
books.append(b!)
}
}
print ("Query>> \(books.count)" + " books loaded, callback completion")
completion(books)
}
}
task.resume()
Console output when the request is not successful:
Query:>> url: Optional(localhost:3001/books)
2017-09-07 15:31:45.173596+0800 SimplyRead[76273:2379182] [] nw_endpoint_flow_service_writes [1.1 ::1.3001 ready socket-flow (satisfied)] Write request has 4294967295 frame count, 0 byte count Query>> response from loadBooks
2017-09-07 15:51:41.210732+0800 SimplyRead[76273:2390856] [] tcp_connection_write_eof_block_invoke Write close callback received error: [89] Operation canceled

Related

How to transfer image from server to client with node http header size restrictions

Transferring image (base64 encoded, created with Mapguide server) to client. I am able to output the image to the console and test it is correct. Using Node with npm and Vite for develpment web server. When I try to set imgLegend.src = data; I get this error "431 (Request Header Fields Too Large)" I believe it is the Node default max-http-header-size causing the problem. Have attempted to set --max-http-header-size=80000 with no luck. I am starting my dev server in package.json file like this: "start": "vite --host 0.0.0.0",
Does anyone know of a way around this or a better way to transfer the image from server to client?
here is the relevant code.
Client side:
//add legend
const mapVpHeight = document.getElementById('map').clientHeight;
var url = mgServer + "/Cid_Map/LayerManager.aspx/GetLegendImage";
var values = JSON.stringify({ sessionId: sessionId, mgMapName: mapName, mapVpHeight: mapVpHeight });
var imgLegend = new Image();
//console.log(values);
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: values,
dataType: 'html',
success: function (data) {
console.log(data); //
imgLegend.src = data; //node.js won't allow http header as large as this image, about 18kb
},
error: function (xhr, textStatus, error) {
console.log(textStatus);
}
});
Server Side:
[WebMethod]
public static string GetLegendImage(string sessionId, string mgMapName, int mapVpHeight)
{
string tempDir = System.Configuration.ConfigurationManager.AppSettings["tempDir"];
string legFilePath = tempDir + sessionId + "Legend.png";
string configPath = #"C:\Program Files\OSGeo\MapGuide\Web\www\webconfig.ini";
MapGuideApi.MgInitializeWebTier(configPath);
MgUserInformation userInfo = new MgUserInformation(sessionId);
MgSiteConnection siteConnection = new MgSiteConnection();
siteConnection.Open(userInfo);
MgMap map = new MgMap(siteConnection);
MgResourceService resourceService = (MgResourceService)siteConnection.CreateService(MgServiceType.ResourceService);
map.Open(resourceService, mgMapName);
MgColor color = new MgColor(226, 226, 226);
MgRenderingService renderingService = (MgRenderingService)siteConnection.CreateService(MgServiceType.RenderingService);
MgByteReader byteReader = renderingService.RenderMapLegend(map, 200, mapVpHeight, color, "PNG");
MgByteSink byteSink = new MgByteSink(byteReader);
byteSink.ToFile(legFilePath);
//try this
//byte[] buffer = new byte[byteReader.GetLength()]; //something doesn't work here byteReader doesn't give comeplete image
//byteReader.Read(buffer, buffer.Length);
//loading image file just created, converting to base64 image gives correct image
string legendImageURL = "";
using (Stream fs = File.OpenRead(legFilePath))
{
BinaryReader br = new System.IO.BinaryReader(fs);
byte[] bytes = br.ReadBytes((int)fs.Length);
string strLegendImage = Convert.ToBase64String(bytes, 0, bytes.Length);
legendImageURL = "data:image/png;base64," + strLegendImage;
}
byteReader.Dispose();
byteSink.Dispose();
return legendImageURL;
//return buffer;
}
The 431 status code complains about the header length of your request ..
trace the request in your browsers dev tool network tab and study the header fields in your request in some special cases if your cookies get set to often with unique key value pairs this could be the problem...
May be you can copy and share the request response from your browsers network tab to provide some detailed information... especially the request response of the endpoint and look up the cookie/session storage maybe you find some suspicious stuff.
Good look :)

Expiration of CachedURLResponse not working

I was trying to set custom headers for 'Cache-Control' to achieve a cache at client side(server side has 'Cache-Control: no-cache'). Trying to achieve following two major things.
Response of some of the end points should be cached in memory and
should have an expiration(user defined)
Once expiration time is over, then app should ignore the cache and should fetch data from server.
I followed this link and was able to achieve first target but somehow even after expiry app is still using cache and not triggering any API calls. Not sure if 'max-age' set in header is ignored by the app. Please guide me if I am missing something here.
Here are the code snippets.
Session Configuration:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Request:
if let urlPath = URL(string: <WEB_API_END_POINT>){
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
aRequest.addValue("private", forHTTPHeaderField: "Cache-Control")
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}
Caching logic:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: #escaping (CachedURLResponse?) -> Void) {
if proposedResponse.response.url?.path.contains("/employees") == true {
let updatedResponse = proposedResponse.response(withExpirationDuration: 60)
completionHandler(updatedResponse)
} else {
completionHandler(proposedResponse)
}
}
CachedURLResponse Extension:
extension CachedURLResponse {
func response(withExpirationDuration duration: Int) -> CachedURLResponse {
var cachedResponse = self
if let httpResponse = cachedResponse.response as? HTTPURLResponse, var headers = httpResponse.allHeaderFields as? [String : String], let url = httpResponse.url{
headers["Cache-Control"] = "max-age=\(duration)"
headers.removeValue(forKey: "Expires")
headers.removeValue(forKey: "s-maxage")
if let newResponse = HTTPURLResponse(url: url, statusCode: httpResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: headers) {
cachedResponse = CachedURLResponse(response: newResponse, data: cachedResponse.data, userInfo: headers, storagePolicy: .allowedInMemoryOnly)
}
}
return cachedResponse
}
}
Was able to fix it at my own. Still sharing the answer in case if helps someone else in need.
Added 'Cache-Control' response header in server response and there we have 'max-age: 60', which indicates that response can be valid till 60 seconds only. So till 60 seconds, app will cache that data and after 60 seconds if making another request, this will fetch fresh data from server.
With that, At client side other than defining your cache policy, nothing else is required.
You can do this either for entire URL Session:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .useProtocolCachePolicy
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Or can do it for specific request.
if let urlPath = URL(string: <WEB_API_END_POINT>) {
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}

Vertx client for sending get request with parameters

I am writing client in Vertx for sending get request with two string parameters but i am receiving empty list from server. If i write request to another service which is on the same path but this service is not receiving any parameters the response is ok and the data is properly returned. The problem is with mapping the parameter on server side with .addQueryParam the parameter is not mapped well on server side. any help?
WebClient client = WebClient.create(vertx);
client
.get(80, "localhost", "/mainpath/path1")
.addQueryParam("startDate", "1459926000")
.addQueryParam("endDate", "1459926900")
.send(ar -> {
if (ar.succeeded()) {
HttpResponse<Buffer> response = ar.result();
JsonArray body = response.bodyAsJsonArray();
System.out.println("Received response with status code " + response.statusCode() + " with body " + body.toString());
} else {
System.out.println("Something went wrong " + ar.cause().getMessage());
}
});
}

Use nlapiRequestURL to make a request to a Service

How do you use nlapiRequestURL to make a request to a service? My attempt below is failing with the error: UNEXPECTED_ERROR (output from NetSuites script execution log).
My service is set to run without login and works correctly when I directly access it through a browser using its url. Its just the request through nlapiRequestURL thats failing.
Any idea what could be going wrong?
// This code executes in Account.Model.js (register function)
// I am using my own netsuite user credential here
var cred = {
email: "MY_NETSUITE_EMAIL"
, account: "EXXXXX" // My account id
, role: "3" // Administrator
, password: "MY_NETSUITE_PASSWORD"
};
var headers = {"User-Agent-x": "SuiteScript-Call",
"Authorization": "NLAuth nlauth_account=" + cred.account + ", nlauth_email=" + cred.email +
", nlauth_signature= " + cred.password + ", nlauth_role=" + cred.role,
"Content-Type": "application/json"};
var payload = {
type: 'is_email_valid'
, email: 'spt015#foo.com'
};
// A raw request to the service works fine:
// http://mywebsite.com/services/foo.ss?type=is_email_valid&email=spt015#foo.com
// Error occurs on next line
var response = nlapiRequestURL(url, payload, headers);
You are attempting to call a non-Netsuite url with Netsuite authentication headers. You do not need that unless for some reason of your own you have implemented NS-style authorization on your service.
nlapiRequestURL does not automatically format a payload into a query string. If your service takes a posted JSON body then you need to call JSON.stringify(payload) e.g
var response = nlapiRequestURL(url, JSON.stringify(payload), headers);
If your service needs a query string like in your example then you need to construct a query string and append it to your service url. e.g.
var qs = '';
for(var k in payload) qs += k +'='+ uriEncodeComponent(payload[k]) +'&';
var response = nlapRequestURL(url +'?'+ qs.slice(0,-1), null, headers);
I would suggest changing your nlapiRequestURL to a GET instead of POST, and add the parameters to the url instead. Your function call will look like this instead.
nlapiRequestURL(url, null, headers, "GET")

Node-red "no response object"

I'm trying to create a http get node that takes form data and then creates an http post to another web site using a rest API. I'm able to get the http call to return the correct payload however the http response node is giving a "no response object" error in the debug tab and the client never returns. Do I specifically need to do something to pass the response object so the call sends the correct response object to the http response node?
Here is the flow:
[{"id":"7011e7a9.8fee18","type":"http request","name":"","method":"use","ret":"obj","url":"","x":499,"y":84,"z":"51693a88.ae96c4","wires":[["efea497c.1015b8"]]},{"id":"589f2a18.a760d4","type":"function","name":"","func":"var myprocess = context.global.process;\nmyprocess.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\nvar dev_url = \"https://website.com:22411/central/api/qracore/browses?browseId=mfg:gp239\";\n \nvar msg = {\n \"method\" : \"GET\",\n \"url\" : dev_url,\n \"headers\" : {\n \"Authorization\": \"Basic abWZnQHFhZC5jb206=\",\n \"Origin\" : \"mybox.com\"\n }\n};\n \nreturn msg;","outputs":1,"noerr":0,"x":323,"y":154,"z":"51693a88.ae96c4","wires":[["7011e7a9.8fee18"]]},{"id":"9f65b624.609a48","type":"debug","name":"","active":true,"console":"false","complete":"payload","x":810,"y":211,"z":"51693a88.ae96c4","wires":[]},{"id":"38e4c04d.c71b4","type":"http in","name":"","url":"/workOrders","method":"get","swaggerDoc":"","x":152,"y":190,"z":"51693a88.ae96c4","wires":[["589f2a18.a760d4"]]},{"id":"e5ee54b2.1a11a8","type":"http response","name":"","x":770,"y":96,"z":"51693a88.ae96c4","wires":[]},{"id":"efea497c.1015b8","type":"function","name":"","func":"var newMsg = { payload: msg.payload };\nreturn newMsg;","outputs":1,"noerr":0,"x":582,"y":214,"z":"51693a88.ae96c4","wires":[["e5ee54b2.1a11a8","9f65b624.609a48"]]}]
The key is to pass it from node to the other. You do that by using the req and the res from the msg to pass to the output.
var newMsg = { payload: msg.payload };
//return [msg, newMsg];
newMsg.user = msg.req.body["user"];
newMsg.req = msg.req;
newMsg.res = msg.res;
return newMsg;

Resources