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

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.

Related

Upload file from AngularJS to Spring Boot

I'm using AngularJS to make my front end and Java with Spring Boot to make my back end. I'm trying to import/upload a xlsx, xls, or ods file from the Angular to my Java, but whatever I do, the request doesn't send my file!
Java endpoint:
#PostMapping(value = "/upload/{type}")
#ResponseBody
private ResponseEntity<List<Rota>> importFile(#PathVariable("type") String type,
#RequestParam(required = false, value = "file") MultipartFile fileParam,
#RequestBody MultipartFile file) {
System.out.println("File: " + file.getName());
if(type.toUpperCase().equals("XLSX")){
System.out.println("XLSX!");
}else if(type.toUpperCase().equals("XLS")){
System.out.println("XLS!");
}else if(type.toUpperCase().equals("ODS")){
System.out.println("ODS!");
}else{
System.out.println("OPS!");
return new ResponseEntity<>(new ArrayList<>(), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK);
}
My request-id made using an Angular class prepared to only make requests. I'll post here the code that we use normally in the project and the code that actually worked but I can't use it.
DataFactory:
DataFactory.POST = function (url, entity, config = null) {
if (url && entity) {
return $http
.post(url, entity, config)
.then(res => $q.resolve(res.data), error => $q.reject(error));
}
};
The code that actually worked:
var xhr = new XMLHttpRequest;
xhr.open('POST', `${URL.CTM_ODS()}/rotas/upload/${type}`, true);
xhr.send(formData);
When I use Postman, sending the file through the body, the back end receives null, but when I use form-data from Postman, works fine!
Using the DataFactory I got the following stack on my back end:
WARN 16796 --- [p1002886236-126] org.eclipse.jetty.http.HttpParser : badMessage: java.lang.IllegalStateException: too much data after closed for HttpChannelOverHttp#78fd8670{r=2,c=false,a=IDLE,uri=}
Found the answer in this video: https://www.youtube.com/watch?v=vLHgpOG1cW4
My problem was that the AngularJS deserialize the file! So, what I've made was just put a config object saying to not do that:
DataFactory.POST(`${URL.CTM_ODS()}/rotas/upload/${type}`, formData, { transformRequest: angular.indentity, headers: { 'Content-Type': undefined } }).then(response => {
delete vm.importacao.arquivoCsv;
console.log('Response: ', response);
LoadingManager.hide();
});
As you can see, the difference is on the header object, can you see that we pass the content type as undefined and transformRequest: angular.indentity? That worked for me! If anyone has other way to do so, feel free to comment! Thanks and have a nice day!

JSON POST request sends only id

Hello Everyone I couldn't find any solution so here is my question
I tried to write POST Request and tried to POST data as JSON, it works
but I only get new Object in JSON file with new ID, nothing else is being sent.
This is ReaactJS app to be exact
var url = 'http://localhost:3000/applications'; //Configurable endpoint
var request = new XMLHttpRequest();
var isSent = false;
var data = {
name : this.state.name,
age : this.state.age,
prefix : this.state.prefix,
email : this.state.email
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function () {
isSent = true;
console.log(this.responseText);
};
xhr.send(data);
This is my way to do so
You should check out new fetch API.
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set.
Please check Fetch: POST json data
The problem night be with the formatting of the .rest file
For example
POST http://localhost:3001/api/persons
Content-Type: application/json
{
"name" : "fbgjdbh",
"number" : 89475947
}
this worked for me. You need to leave an empty line after the content-type, then specify the object.
The following was not working for me and was only sending the id.
POST http://localhost:3001/api/persons
Content-Type: application/json
{
"name" : "fbgjdbh",
"number" : 89475947
}

How To Setup Minimalist Authentication In Rails with React?

I am trying to set up a minimal layer of authentication between my Rails backend and my React front end, but I am running into some problems.
I cannot seem to find the cookie key value that the server passes down to my client. In the network tab, I see it in the response: Set-Cookie:_skillcoop_session=...., but when I use js-cookie to look for the above cookie, _skillcoop_session, I only see one called identity-token=... and its value is different from _skillcoop_session. How do I access _skillcoop_session in the browser?
What header key do I pass up to the server to signal to my backend to use 'this' header key to match up with the session it has stored off? In this post, Justin Weiss seems to suggest that I make the request to the server with a header like: Cookie: _skillcoop_session=....
Am I doing this all wrong? Would I be better off using a gem like devise?
Also in order to load the session in my other controllers, I have had to do something like session['init'] = true, and I learned to do this from this SO post. This seems hacky. Why do I have to manually reload the session in separate controller actions after I've set it previously in a different controller action in a different request?
I'm currently just stubbing out the user and the authentication -- all I want to do to get the plumping in place is set a session[:user_id] and be able to read that session data in other controller actions. For this I have two main files for consideration: UsersController and Transport.js. In UsersController I am just stubbing the session[:user_id] with the number 1 and in Transport.js I'd like to pass the cookie received from the server so that the backend can maintain a session between requests with a client.
Here is my controller:
class UsersController < ApplicationController
def create
session[:user_id] = 1
render json: user_stub, status: :ok
end
def show
puts "user id: #{session[:user_id]}"
# should return, 1, but is returning, nil...why?
render json: user_stub, status: :ok
end
private
def user_stub
{
id: 1,
email: params['email'] || 'fakeemail#gmail.com',
password: params['password'] || 'fake password'
}
end
end
Here is the main location of my app where I make my request to the server - it's in an abstraction I call Transport.js:
require('es6-promise').polyfill();
require('isomorphic-fetch');
var cookie = require('js-cookie');
const GET = 'GET';
const POST = 'POST';
function Transport() {
}
Transport.prototype.get = function(url, options = {}) {
return this.query(GET, url, null, options);
};
Transport.prototype.post = function(url, dataString, options = {}) {
return this.query(POST, url, dataString, options);
};
Transport.prototype.query = function(method, url, dataString, options = {}) {
var data;
if (dataString) {
data = JSON.parse(dataString);
}
switch(method) {
case GET:
return fetch(url, Object.assign({headers: {'Cookie': cookie.get('_skillcoop_session')}}, options, {
method: method
}));
case POST:
return fetch(url, Object.assign({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}, options, {
method: method
}));
default:
throw new Error("This HTTP Method is not supported.");
}
};
module.exports = Transport;
According to this SO post, one cannot access the Set-Cookie header in JS. Thus, I suppose my attempts to handle Set-Cookie in the response headers was a fools effort.
According to the NPM package that I'm using to make HTTP requests, I need to pass {credentials: 'same-origin'} key value pair in the second argument to fetch, which will 'automatically send cookies for the current domain'. That did the trick -- the session object is available and contains the user_id that was set in the session in the previous request in a different action.
Yes. I changed up how I approached this problem. I leaned very heavily on this Reddit post. In short, I use ruby-jwt on the backend and store the token in localStorage on the front end. Each request out to the server will include the token in a header AUTHORIZATION.
In following steps 1 and 2, it looks like I no longer have to 'reload the session'.

Get access to the "header" in Angular's $htttpBackend when using the "whenPost - respond" method

I am using the $httpBackend to mock a backend and I want to get access to the headers when I have a request:
$httpBackend.whenPOST('/movies').respond(function(method, url, data) {
// How do I get access to the headers.
// I want to check the headers and retrieve a token.
var params = angular.fromJson(data),
movie = MoviesDataModel.addOne(params),
movieId = movie.id; // get the id of the new resource to populate the Location field
return [201, movie, { Location: '/movies/' + movieId }];
});
Basically, I want to check a value from the headers before I send the data.
Any ideas on how to do it?
Got this from the manual
httpBackend.whenPOST('/movies',data, function(headers){
return headers['Authorization'] == 'xxx';
})
.respond(function(method, url, data) {

415 (Unsupported Media Type) in $http.post method

I'm quite new to REST and AngularJS, but after several hours of googling I couldn't find any answer to my question:
I'm trying to do a POST request from my angularjs frontend to my backend implemented in java (using JPA).
When I'm trying to create a json-object and to do a POST I always get the 415 (Unsupported Media Type) error.
(Actually I don't even get "into" the scope of the service (i.E. "IN SERVICE" doesn't get printed to the console)..
If I add postData.toJSON(), it actually gets "POSTed", but arrives null ...
how do I have to format my 'postData' in Order to succesfully get POSTed?
(I also tried to write the Date-properties without ' " ' - no luck...)
Thank you for your help!
FrontEnd:
app.controller('WorkController', function($scope, $http) {
$scope.saveWork = function () {
var postData = {
"status" : "OPEN",
"startDate": "1338364250000",
"endDate": "1336364253400",
"WorkText" : "Test"
};
$http.post("http://localhost:8080/service/v1/saveWork", postData)
.success(function(data, status, headers, config){
console.log("IN SAVE WORK - SUCCESS");
console.log(status);
})
.error(function(){
console.log("ERROR IN SAVE WORK!");
})
}
});
Service:
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response save(WorkDto wo){
System.out.println("IN SERVICE");
if(ass == null){
System.out.println("Could nor persist work- null");
return Response.noContent().build();
} else{
Work workDao = WorkTransformator.transform(wo);
workDao.persist();
return Response.ok().build();
}
}
Instead of building and sending a parsed JSON object, create a javascript object and send that in your post body. You can reuse your postData object, but try removing the "" surrounding properties names.
Try this:
var postData = {
status : "OPEN",
startDate: "1338364250000",
endDate: "1336364253400",
workText : "Test"
};
UPDATE
Looks like the above doesn't work by itself. I thought that the Content-Type would be infered.
Can you try to do the post request this way :
$http({
method: 'POST',
url: 'http://localhost:8080/service/v1/saveWork',
data: postData,
headers: {
'Content-Type': 'application/json'
}}); // complete with your success and error handlers...
// the purpose is to try to do the post request explicitly
// declaring the Content-Type you want to send.
UPDATE 2
If this didn't work, compose a post request using Fiddler, and check what's the response.
Here's some pointers:
Download Fiddler2 if you dont already have it
Compose a request like in the screenshot below
You can then check on the pane on the left for what was the server response code. Double click that line (Ignore the error code on the screenshot...you should be getting a 415)
After double-clicking the response line, you can check and browse for more details on the right pane:
If you can successfuly post with a «manufactured» JSON object then the problem resides on your Angular code. If not, it's certainly something wrong with your Rest Service configuration.
You can also inspect the details of your POSTS made with the Angular app in Fiddler2. That should give you a good insight of what's going on.
If you're into it, you can then update your question with some screenshots of your Angular app requests. That will certainly help us to help you :)
I finally managed to find the cause of my error!
In my Rest-Service, I directly expected my java-class as parameter. (I thought this would be parsed/deserialized automatically). Quite naive I think... :)
In order to get it working I had to:
-Expect a String as Parameter in my #POST service
-Deserialize it (using GSON)
Here is the (now working) service:
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response save(String wo){
if(wo == null){
System.out.println("Could nor persist work- null");
return Response.noContent().build();
} else{
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HHmm:ssZ").create();
WorkDto dto = gson.fromJson(wo, WorkDto.class);
Work workDao = WorkTransformator.transform(dto);
workDao.persist();
return Response.ok().build();
}
}
Thanks again António for your help!

Resources