ReactiveX / rxjs

A reactive programming library for JavaScript
https://rxjs.dev
Apache License 2.0
30.84k stars 3.01k forks source link

Missing response value on ajax result. #2007

Open maierson opened 8 years ago

maierson commented 8 years ago

Hi guys,

I'm trying to do a simple ajax call and get a response but i get an answer with null response value.

RxJS version: "rxjs": "^5.0.0-beta.12"

Code to reproduce:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/dom/ajax';

 Observable.ajax({
        url: window.location.origin + "/api/book/load",
        method: "POST",
        responseType: "json",
        body: {
            key: "userKey",
            sessionId: "oisudofiu"
        },
        headers: {
            "Accept": "application/json, text/plain, */*",
            "cache-control": "no-cache"
        }
    }).subscribe(
        xhr => console.log("LOADED ", JSON.stringify(xhr, null, 2)),
        error => console.log("ERROR ", JSON.stringify(error, null, 2)),
        () => console.log("COMPLETE"))

Expected behavior: Get a response with result information

Actual behavior: Missing response information on Ajax response

{
  "originalEvent": {
    "isTrusted": true
  },
  "xhr": {},
  "request": {
    "async": true,
    "crossDomain": false,
    "withCredentials": false,
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "cache-control": "no-cache",
      "X-Requested-With": "XMLHttpRequest",
      "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    "method": "POST",
    "responseType": "json",
    "timeout": 0,
    "url": "http://localhost:8888/api/book/load",
    "body": "key=userKey&sessionId=oisudofiu"
  },
  "status": 200,
  "responseType": "json",
  "response": null
}

Additional information: If i make the same call with axios against the same (jersey) REST endpoint I get back the correct data. The endpoint is configured to ignore params and respond with "Result of book loaded" string. Here is the axios response:

{
  "data": "\"Result of book loaded\"",
  "status": 200,
  "statusText": "OK",
  "headers": {
    "date": "Wed, 05 Oct 2016 16:27:40 GMT",
    "cache-control": "no-cache",
    "expires": "Mon, 01 Jan 1990 00:00:00 GMT",
    "server": "Development/1.0",
    "content-length": "27",
    "content-type": "application/json;charset=utf-8"
  },
  "config": {
    "transformRequest": {},
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Content-Type": "application/x-www-form-urlencoded"
    },
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "method": "post",
    "baseURL": "http://localhost:8888/api",
    "url": "http://localhost:8888/api/book/load",
    "data": "key=user&sessionId=password"
  },
  "request": {}
}

Thanks a lot for any help.

(EDIT by @blesh to add code color formatting)

benlesh commented 8 years ago

Interesting, it makes me wonder if it has something to do with it being a POST or not.

benlesh commented 8 years ago

@maierson is this, by chance, something that is open source that we can look at? Do you have a minimum replication case we can see? Or perhaps could we get a copy of the raw HTTP response so we can try to spoof it locally and replicate the issue?

maierson commented 8 years ago

Hi Ben,

Thanks for looking into this. The project is not open source but I'm at the beginning of porting a large project to react and want to get all my (ahem) ducks in a row before I do all the porting. I'll put a boilerplate together as soon as I get a chance.

Some more details. I'm using typescript 2.0, redux-observable. I'm calling a resteasy java endpoint. The error also misses the error message. If I wrap a promise with Observer.fromPromise(promise) then I am able to get the response and error messages but that kind of defeats the point of being able to cancel out of it.

I've also tried with Rx v4 + rx-dom Rx.DOM.ajax() and there I get the response message but still have no access to the error message.

I'll look into getting you the raw HTTP response - just have to restore the setup.

maierson commented 8 years ago

@blesh - i did some more digging and figured out the reason for result missing. It's because the server actually sends back a text response and the post call was configured to get a json call. Once i change the ajax call configuration to responseType: "text" I am able to get the result.

The axios promise is configured with "Accept": "application/json, text/plain, */*" header so it's why there is no issue there.

This probably should raise an error instead of returning a response with empty result.

The error not providing a message issue still remains. Here is the HTTP call info (see the Response containing the error message returned from the server):

Response Headers:
    HTTP/1.1 400 Bad Request
    Server: Development/1.0
    Date: Thu, 06 Oct 2016 00:06:10 GMT
    Content-Length: 94
    Content-Type: text/plain
    Cache-Control: no-cache
    Expires: Mon, 01 Jan 1990 00:00:00 GMT
Request Headers:
    POST /.............. HTTP/1.1
    Host: localhost:8888
    Connection: keep-alive
    Content-Length: 23
    Origin: http://localhost:8888
    X-Requested-With: XMLHttpRequest
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Accept: */*
    Referer: http://localhost:8888/story/aghrb2xvcmh1YnIlCxIHQXBwVXNlchiAgICAgICACgwLEgRCb29rGICAgICAyP4LDA
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8
    Cookie: localhost=2290043D016369E05F850832B69C7FF471016480D081D8926A3A7CA80E90C107
Form data:
    key=test&sessionId=null
Response:
    The id of the item you are trying to request is invalid on our system. Please try again.

And this is the error payload from the observer's catch method

{
  "message": "ajax error 400",
  "xhr": {},
  "request": {
    "async": true,
    "crossDomain": false,
    "withCredentials": false,
    "headers": {
      "X-Requested-With": "XMLHttpRequest",
      "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    "method": "POST",
    "responseType": "text",
    "timeout": 0,
    "url": "http://localhost:8888/....................",
    "body": "key=test&sessionId=null"
  },
  "status": 400
}
benlesh commented 8 years ago

I see. This seems like something we can fix. I'll give it some thought.

jjasonclark commented 8 years ago

I have run into the same issue. The code works as expected under Chrome. But under Firefox the Ajax response is blank. Changing the responseType to text does show me the response. Seems it only fails on the JSON type.

Hope the extra information helps.

adregan commented 8 years ago

Whew, am I glad for your comment @jjasonclark—at work I use Firefox Dev, but at home, I use the regular FF (version 50), and I thought I was going crazy ("this thing was just working at the office!").

I can confirm that I am getting an null for the response field, but only in Firefox 50. I haven't experienced it at all on the Dev edition or in chrome.

cescoferraro commented 6 years ago

I am working on 3rd party API that talks json, but sends simple string as errors, which should be fine hence a string is a valid json format. But its not because the error object always return null if the response is a string. I assume responseType is json by default.

using "rxjs": "^5.5.2"

muthukumarse commented 6 years ago

Guys, any workaround for this issue, I got this issue too

when server trying to redirect by sending ..... xhr.response - null xhr.reponseType - json instead of text/html

so there is no way to redirect to login based on the response.

aaronjensen commented 6 years ago

Found a benchmark comparing xhr + JSON.parse vs responseType = 'json'. AFAICT, there's not much reason to use responseType = 'json'.

useFetch x 4.86 ops/sec ±1.39% (28 runs sampled)
useXhr x 9.83 ops/sec ±1.42% (49 runs sampled)
useXhrWithResponseTypeJson x 5.24 ops/sec ±1.82% (30 runs sampled)

(I reran this benchmark on Chrome 69)

The workaround for us would be to set responseType: 'text' and parse the json ourselves:

ajax({
  url: 'https://api.trello.com/1/batch/', 
  responseType: 'text',
  }).pipe(map(r => ({...r, response: JSON.parse(r.response), responseType: 'json'})))
  .subscribe(console.log, e => {
    console.log('fail', e.response)
  }, console.log)

Example here: https://stackblitz.com/edit/typescript-4y5sch?file=index.ts

Maybe rxjs make the IE fallback code the primary code and remove the responseType setting?

muthukumarse commented 6 years ago

@aaronjensen

Exactly the same I did to solve my problem.

We have to be careful if requestType is Json or text we can parse. Otherwise i.e. blob we just move on

rudolfschmidt commented 5 years ago

Hey guys, a guly workaround is not a solution. ajax.get or ajax.post need to have a response value if ther is any given by the server. I face the same issue here using ajax.post to send params and receive a string. its just null. Its a no go bug. Please fix it as soon as possible or remove the method completely if you cannot make it work to not confuse further developers.

taylor-cedar commented 5 years ago

We have unfortunately used a forked version of RXJS because of this bug. Has there been any pushback to submit a PR to fix?

kochmaxence commented 5 years ago

What's the status on this? It's opened since 2016 and seems like it's still an issue.

iamleson98 commented 4 years ago

hey guys! What an interesting problem! This issue was opened since 2016, back in the day, i did'nt know anything about coding. Today i know it and wanna share a little bit.

1, loading chartjs from a CDN using rxjs/ajax:

ajax("https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js")
    .subscribe(
        data => {
            console.log(data.response);
        },
        error => console.error(error)
);
/*
RESULT:  null
*/

I tended to give up and made friend with axios But googling let me found this issue 2, loading with some config configured:

ajax({
  url: "https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js",
  responseType: "blob"
}).subscribe(
  data => {
    const script = document.createElement("script");
    script.src = URL.createObjectURL(data.response);
    document.body.appendChild(script);
    script.onload = function() {
      console.log(typeof Chart);
    };
  },
  error => console.error(error)
);

/*
   RESULT: 'function' meaning chartjs was fetched successfully. Lol
*/

Thank you for your help!

laosandegudai commented 3 years ago

So it will continue?