In ELM, a GET request can't have a body, or is it? - request

Http.request seems to ignore body when the method is GET
init : () -> ( Model, Cmd Msg )
init _ =
( Loading
, Http.request
{ method = "GET"
, headers = []
, url = "http://127.0.0.1"
, body = Http.stringBody "text/plain" "Hello World!"
, expect = Http.expectWhatever Sent
, timeout = Nothing
, tracker = Nothing
}
)
The sent request has no body (when inspected with browser development tool). 🤔
init : () -> ( Model, Cmd Msg )
init _ =
( Loading
, Http.request
{ method = "POST" {- CHANGED TO POST -}
, headers = []
, url = "http://127.0.0.1"
, body = Http.stringBody "text/plain" "Hello World!"
, expect = Http.expectWhatever Sent
, timeout = Nothing
, tracker = Nothing
}
)
But when the method is changed to "POST", it works ! The body contains "Hello World!". 🤨
The API I try to communicate with requires an application/json body in a GET request. Help me 😭 !
PS: Here is what the documentations says:
emptyBody : Body
Create an empty body for your Request. This is useful for GET requests
and POST requests where you are not sending any data.
Which is not clear, because it can be interpreted in two different ways:
This is useful for GET requests and { POST requests where you are not sending any data } .
Or:
This is useful for { GET requests and POST requests } where you are not sending any data.

According to the HTTP specification, GET should not have a body. See for example its description on MDN, which says:
Note: Sending body/payload in a GET request may cause some existing
implementations to reject the request — while not prohibited by the
specification, the semantics are undefined. It is better to just avoid
sending payloads in GET requests.

Elm actually does add the body to the request, but the browser doesn't send it.
To see this, compile this Elm program:
module Main exposing (main)
import Platform
import Http
main =
Platform.worker
{ init = \() -> ((), Http.request
{ method = "GET"
, headers = []
, url = "https://catfact.ninja/fact"
, body = Http.stringBody "text/plain" "hi"
, expect = Http.expectWhatever (\_ -> ())
, timeout = Nothing
, tracker = Nothing
})
, update = \() () -> ((), Cmd.none)
, subscriptions = \() -> Sub.none
}
without optimisation flags to get an index.html file. Part of this file is:
var xhr = new XMLHttpRequest();
xhr.addEventListener('error', function() { done($elm$http$Http$NetworkError_); });
xhr.addEventListener('timeout', function() { done($elm$http$Http$Timeout_); });
xhr.addEventListener('load', function() { done(_Http_toResponse(request.expect.b, xhr)); });
$elm$core$Maybe$isJust(request.tracker) && _Http_track(router, xhr, request.tracker.a);
try {
xhr.open(request.method, request.url, true);
} catch (e) {
return done($elm$http$Http$BadUrl_(request.url));
}
_Http_configureRequest(xhr, request);
request.body.a && xhr.setRequestHeader('Content-Type', request.body.a);
xhr.send(request.body.b);
If you open this file in the browser and step through this part of it in the debugger, you can see that Elm actually does put the given body "hi" in the request.body.b value, passed to xhr.send.
But if you then look in the Network tab in the browser console, you can see that the request doesn't contain a body.
So this is the browser stripping out the body with GET requests, not Elm.

Related

How can I make an authenticated http request and return a stream of objects with dart?

I have to put the API Key in the request header as:
Authorization: Bearer "YOUR API KEY"
This is my code (I'm not sure where to put the header and how)
Future<Stream<Book>> getBooks() async {
var url = ‘example_url’
var client = http.Client();
var streamedResponse = await client.send(
http.Request(‘get’, Uri.parse(url))
);
return streamedResponse.stream
.transform(utf.decoder)
.transform(json.decoder)
.expand(jsonBody) => (jsonBody as Map)[‘results’] )
.map((jsonBook) = Book.fromJson(jsonBook));
}
The Flutter docs https://flutter.io/cookbook/networking/authenticated-requests/ says to use this format for authenticated requests but this is not for streams, this returns a future of an object (Book)
Future<Book> fetchPost() async {
final response = await http.get(
'https://jsonplaceholder.typicode.com/posts/1',
headers: {HttpHeaders.authorizationHeader: "Place your_api_token_here"},
);
final responseJson = json.decode(response.body);
return Book.fromJson(responseJson);
}
You can add custom headers after you created the Request
final request = http.Request('GET'), url)
..headers.addAll(myHeaders);
I have made a custom header using http.Request as follow bellow :
final url =
'https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.6.0-amd64-xfce-CD-1.iso';
final request = Request('GET', Uri.parse(url));
request.headers.clear();
request.headers.addAll({"content-type":"application/json"});

HTTP request in Fibaro Home Center with virtual device

I use the Fibaro Home Center Lite box and have connected several devices to it.
I would like to create a virtual device to send my data to a web server with HTTP request but I don't know how to build the request.
thanks
For VD buttons your code should be like this:
local http = Net.FHttp("192.168.0.32", 80);
local response = http:GET("/core/api/api.php?param=2335");
In Main Loop section (and in scenes) it will have kinda different syntax:
local http = net.HTTPClient();
http : request('https://api.lifx.com/v1/lights/label:Light1/state.json?selector=label:Light1&power=on&brightness=0.7', {
options = {
method = "PUT",
headers = {
['Authorization'] = 'Bearer cce26ealkadj764'
},
data = ""
},
success = function(response) fibaro:debug (response.data) end,
error = function(err) fibaro:debug ("Error:" .. err) end
});

Error 500 and CORS : what's the difference between $http and $window.XMLHttpRequest

In one of my angularJS project, i decided to use ng-droplet, which allow me to create drag-n-drop zone to upload images on server via my api.
After setting the plugin, I tried it, but i get :
XMLHttpRequest cannot load http://myapi.com/api/items. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8383' is therefore not allowed access. The response had HTTP status code 500.
Access-Control-Allow-Origin is set to " * " on server side, and i have no problem to do requests on my other api endpoints.
So I tried to see how the plugin create the request and send it, here's how :
$scope.uploadFiles = function uploadFiles() {
// Reset...
$scope.isError = false;
var httpRequest = new $window.XMLHttpRequest(),
formData = new $window.FormData(),
queuedFiles = $scope.filterFiles($scope.FILE_TYPES.VALID),
fileProperty = $scope.options.useArray ? 'file[]' : 'file',
requestLength = $scope.getRequestLength(queuedFiles),
deferred = $q.defer();
// Initiate the HTTP request.
httpRequest.open('post', $scope.options.requestUrl, true);
/**
* #method appendCustomData
* #return {void}
*/
(function appendCustomData() {
if (!$scope.options.disableXFileSize) {
// Setup the file size of the request.
httpRequest.setRequestHeader('X-File-Size', requestLength);
}
// ...And any other additional HTTP request headers, and POST data.
$scope.addRequestHeaders(httpRequest);
$scope.addPostData(formData);
})();
/**
* #method attachEventListeners
* #return {void}
*/
(function attachEventListeners() {
// Define the files property so that each listener has the same interface.
$scope.listeners.files = queuedFiles;
$scope.listeners.deferred = deferred;
$scope.listeners.httpRequest = httpRequest;
// Configure the event listeners for the impending request.
$scope.listeners.progress();
$scope.listeners.success();
$scope.listeners.error();
})();
// Iterate all of the valid files to append them to the previously created
// `formData` object.
$angular.forEach(queuedFiles, function forEach(model) {
formData.append(fileProperty, model.file);
});
// Voila...
$scope.isUploading = true;
httpRequest.send(formData);
return deferred.promise;
};
As you can see, he's not using $http but $window.XMLHttpRequest.
So I tried to do a file upload in a test controller, on the same end point ( http://myapi.com/api/items ), but this time with $http.
Like so :
var fileInput = document.getElementById('uploadedFile');
var file = fileInput.files[0];
var formData = new FormData();
formData.append('title', "rfsdfsd");
formData.append('description', "rfsdfhgfhfgsd");
formData.append('category', 1);
formData.append('price', 20);
formData.append('file', file);
$http.post('http://myapi.com/api/items', formData, {
headers: {
'Content-Type': undefined
}
});
Note the 'Content-Type': undefined, because when i tried this one, it worked ! Files are actually uploaded and i get a 200 response code.
If i change the Content-Type from undefined to "multipart/form-data", or anything else, or if i remove that line, i got the error from above and upload fail.
In the angular documentation, it is said that setting Content-Type to "undefined" explicitly remove the header :
To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, Use the headers property, setting the desired header to undefined.
It seems that the fact of removing the Content-Type header is part of the reason that it works with $http.
So I also tried with a $window.XMLHttpRequest inside this test controller :
var httpRequest = new $window.XMLHttpRequest();
var fileInput = document.getElementById('uploadedFile');
var file = fileInput.files[0];
var formData = new FormData();
formData.append('title', "rfsdfsd");
formData.append('description', "rfsdfhgfhfgsd");
formData.append('category', 1);
formData.append('price', 20);
formData.append('file', file);
httpRequest.open('post', 'http://myapi.com/api/items', true);
httpRequest.send(formData);
But this one give me the same CORS error as before. I also tried to manually add a Content-Type header like this :
httpRequest.setRequestHeader('Content-Type', undefined);
or
httpRequest.setRequestHeader('Content-Type', null);
or "", " " etc but nothing seemed to work, i always got :
POST http://myapi.com/api/items 400 (Bad Content-Type header value: 'undefined')
So, why does it work with $http and Content-Type set to undefined and how can i modify ng-droplet to make it work ? ( i don't want to rewrite the whole thing using $http instead of $window.XMTHttpRequest).
Why do i get this error when using $window.XMTHttpRequest or $http without Content-Type header or with Content-Type set to anything but undefined ?
Ps : If anyone ask : ng-droplet provides its own way to add POST data, which are needed on the endpoint that i use. Like so :
$scope.interface.setPostData({title: "test", description: 'testetstsetest', price: 20, category: 1});
So it's not the problem source.
EDIT :
I solved it, I forgot that I had an Http Interceptor which automatically add a Authorization header to all the $http requests. The thing that I didn't know was that it wasnt added to $window.XMLHTTPRequest. So I added it manually and now it works.

DART & GAE : Why a POST method send from dart can't be evaluate in GAE?

I have a Dart code used to send an HttpRequest with a POST method to my GAE WepApp2 application. The dart code is executed in chromium and serve by Chrome dev editor. I add in my GAE code some headers to avoid the XHR error in the client side.
The dart code send the datas to my GAE app but I can't read the data with self.request.POST.get("language")) and the app never enter in def post(self): section but with self.request.body I can read the data.
Could you explain that and provide some correction to have a full POST compliant code?
dart:
void _saveData() {
HttpRequest request = new HttpRequest(); // create a new XHR
// add an event handler that is called when the request finishes
request.onReadyStateChange.listen((_) {
if (request.readyState == HttpRequest.DONE &&
(request.status == 200 || request.status == 0)) {
// data saved OK.
print(request.responseText);
}
});
// POST the data to the server
var url = "http://127.0.0.1:8080/savedata";
request.open("POST", url, async: false);
String jsonData = JSON.encode({"language":"dart"});
request.send(jsonData);
}
GAE code in my handler:
def savedata(self):
logging.info("test")
logging.info(self.request.body)
logging.info(self.request.POST.get("language"))
def post(self):
logging.info("test 2")
logging.info(self.request.POST.get("language"))
self.response.headers["Access-Control-Allow-Origin"] = "http://127.0.0.1:49981"
self.response.headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
In Dart, if you don't specify request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") in your HttpRequest, the data is considered by GAE like a bite stream and you can only read them with self.request.body
If you add the Content-Type header in Dart you need also to change the data formating. In my case I mimic a form sending with POST method so I change String jsonData = JSON.encode({"language":"dart"}); by String jsonData = "language=dart2";
IN GAE python I can now read the data with self.request.POST.get("language")
If you need to send a JSON from DART to GAE, you can encode the string like this:
String jsonData = JSON.encode({"test":"valuetest1"});
String datas = "datas=$jsonData";
request.send(datas);
In GAE you can read the datas like this:
my_json = json.loads(self.request.POST.get("datas"))
logging.info(my_json["test"])
The complete code:
Dart
void _saveData2() {
String url = "http://127.0.0.1:8080/savedata";
HttpRequest request = new HttpRequest()
..open("POST", url, async: true)
..setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
..responseType = "arraybuffer";
String jsonData = JSON.encode({"test":"valuetest1"});
String datas = "datas=$jsonData";
request.send(datas);
}
GAE
class PageHandler(webapp2.RequestHandler):
def savedata(self):
self.response.headers.add_header('Access-Control-Allow-Origin', '*')
self.response.headers['Content-Type'] = 'application/json'
#logging.info(self.request)
my_json = json.loads(self.request.POST.get("datas"))
logging.info(my_json["test"])

In GAE Channel API the onmessage is not called

I am building an app for GAE using python API. It is running here. It is a multi-player game. I use the Channel API to communicate game state between players.
But in the app engine the onmessage handler of the channel is not called. The onopen handler is called. onerror or onclose are not called as well. Weird thing is this works perfectly in the local development server.
Is it possible that something like this can work on the development server but not in the app engine itself?
I'll be really really glad if someone can look into following description of my app and help me to figure out what has happened. Thank you.
I looked into this and this questions, but I haven't done those mistakes.
<script>
sendMessage = function(path, opt_param, opt_param2) {
path += '?g=' + state.game_key;
if (opt_param) {
path += '&' + opt_param;
}
if (opt_param2) {
path += '&' + opt_param2;
}
var xhr = new XMLHttpRequest();
xhr.open('POST', path, true);
xhr.send();
};
Above function is used to make a post request to the server.
onOpened = function() {
sendMessage('/resp');
console.log('channel opened');
};
Above is the function I want to be called when the channel is open for the first time. I send a post to the '/resp' address.
onMessage = function(m) {
console.log('message received');
message = JSON.parse(m.data);
//do stuff with message here
};
I want to process the response I get from that request in the above function.
following are onerror and onclose handlers.
onError = function() {
console.log('error occured');
channel = new goog.appengine.Channel('{{ token }}');
socket = channel.open();
};
onClose = function() {
console.log('channel closed');
};
channel = new goog.appengine.Channel('{{ token }}');
socket = channel.open();
socket.onopen = onOpened;
socket.onmessage = onMessage;
socket.onclose = onClose;
socket.onerror = onError;
</script>
This script is at the top of body tag. This works fine in my local development server. But on the app engine,
onOpen function is called.
I can see the request to /resp in the sever logs.
but onMessage is never called. The log 'message received' is not present in the console.
this is the server side.
token = channel.create_channel(user.user_id() + game.user1.user_id() )
url = users.create_logout_url(self.request.uri)
template_values = {
'token' : token,
'id' : pid,
'game_key' : str(game.user1.user_id()),
'url': url
}
path = os.path.join(os.path.dirname(__file__), 'game.html')
self.response.out.write(template.render(path, template_values))
and this is in the request handler for '/resp' request. My application is a multi-player card game. And I want to inform other players that a new player is connected. Even the newly connected player will also get this message.
class Responder(webapp2.RequestHandler):
def post(self):
user = users.get_current_user()
game = OmiGame.get_by_key_name(self.request.get('g'))
if game.user1:
channel.send_message(game.user1.user_id() + game.user1.user_id() , create_message('%s joined.' % user.nickname()))
if game.user2:
channel.send_message(game.user2.user_id() + game.user1.user_id() , create_message('%s joined.' % user.nickname()))
EDIT : user1 is the user who created the game. I want tokens of other players' to be created by adding the user1's user_id and the relevant users user_id. Could something go wrong here?
So when I try this on the local dev server I get these messages perfectly fine. But on the GAE onMessage is not called. This is my app. When the create button is clicked page with above script is loaded and "playernickname connected" should be displayed.
The channel behavior on the dev server and production are somewhat different. On the dev server, the channel client just polls http requests frequently. On production, comet style long polling is used.
I suspect there may be a problem with making the XHR call inside the onOpened handler. In Chrome at least, I see that the next talkgadget GET request used by the channel API is cancelled.
Try calling sendMessage('/resp') outside of the onMessage function. Perhaps enqueue it to get run by using setTimeout so it's called later after you return.

Resources