canjs / can-connect

Model layer utilities for every JavaScript framework! Assemble real-time, high performance, restful data connections.
https://canjs.com/doc/can-connect.html
MIT License
29 stars 16 forks source link

Request Body format as JSON instead of URL form encoded #348

Closed Nithanaroy closed 6 years ago

Nithanaroy commented 7 years ago

How often can you reproduce it?

Description: We are using can-connect, ^1.3.6. Observed the recent change in model.save() behavior from sending JSON data (to server) to url-encoded format. We are using baseMap() with our models and unable to save due to request data format change. Any ideas on how to change this setting in baseMap?

Steps to reproduce:

  1. Include a JS Bin (or equivalent) link if possible
  2. Detail the exact steps taken to produce the problem
  3. Include a gif if possible; you can use LICEcap to make a gif: http://www.cockos.com/licecap/

Create a Model with can-connect 1.5.7 and call modelInstance.save(). Observe the request body format.

Expected results: JSON request body

Actual results: form encoded request body

Environment:

Software Version
can-connect version 1.5.7
Browser Chrome
Operating system MacOS 10.12.6
justinbmeyer commented 7 years ago

Something changed between 3.8 and 3.9:

https://jsbin.com/zicesev/edit?html,js,output

Thanks for reporting.

justinbmeyer commented 7 years ago

Ok, so CanJS behaves the right way. For example, this JSBin doesn't load jQuery and works right:

https://justinbmeyer.jsbin.com/zicesev/2/edit?html,js,output

This one does load jQuery:

https://justinbmeyer.jsbin.com/himison/2/edit?html,js,output

and works wrong.

The fix would be to make sure can.ajax is being used like:

var Type = can.DefineMap.extend({
  id: "string",
  name: "string"
});
Type.connection = can.connect.baseMap({
  Map: Type,
  url: "/place"
});
Type.connection.ajax = can.ajax;
new Type({name: "something"}).save()
Nithanaroy commented 7 years ago

How are you checking the request body for .save() in the above JS Bin?

justinbmeyer commented 7 years ago

How are you checking the request body for .save() in the above JS Bin?

using the network tab:

js_bin_-_collaborative_javascript_debugging

justinbmeyer commented 7 years ago

What's also very odd is that if you do:

Type.connection = can.connect.baseMap({
  Map: Type,
  url: {resource: "/place", contentType: "application/json"}
});

we call jQuery with:

js_bin_-_collaborative_javascript_debugging

And it still sends the data out as form data. I wonder if this is something that changed with jQuery?

justinbmeyer commented 7 years ago

My guess is that jQuery doesn't see contentType: "application/json" and automaticallyJSON.stringify` its data. I'm not sure what to do about this.

Nithanaroy commented 7 years ago

Is there a way temporarily solve this while the complete solution is figured out? Like versioning down the can-connect library etc?

phillipskevin commented 7 years ago

@Nithanaroy did you try setting Type.connection.ajax to use can-ajax like mentioned in https://github.com/canjs/can-connect/issues/348#issuecomment-327936491?

justinbmeyer commented 7 years ago

I showed the fix above

Nithanaroy commented 6 years ago

For setting the connection,

Type.connection.ajax = can.ajax;

should I import entire can like import can or can I just import can.ajax property?

chasenlehara commented 6 years ago

It sounds like you’re already using the individual modules (like can-connect), so I would npm install --save can-ajax and then:

import ajax from 'can-ajax';

// ...your model code

Type.connection.ajax = ajax;
Nithanaroy commented 6 years ago

A solution

...
    "url": "/api/v1/entity",
    "method": "POST",
    "contentType": "application/json",
    "data": JSON.stringify( entityObjDataToSave ),

This jQuery snippet correctly sends the request body as JSON

May be this can be added to can-ajax wrapper of jQuery?

rjgotten commented 6 years ago

@justinbmeyer

My guess is that jQuery doesn't see contentType: "application/json" and automatically JSON.stringify its data. I'm not sure what to do about this.

You can register a pre-filter to force jQuery to do the right thing for a JSON content type and compatible HTTP methods.

E.g.

$.ajaxPrefilter( function ( options, orig, xhr ) {
  // If data should be processed internally and the content-type and
  // http method indicate JSON, ensure that the data is delivered as
  // a JSON-encoded string and not as form-encoded values.
  if ( options.processData
    && /^application\/json((\+|;).+)?$/i.test( options.contentType )
    && /^(post|put|delete)$/i.test( options.type )
  ) {
    options.data = JSON.stringify( orig.data );
    options.processData = false;
  }
});