cancel
Showing results for 
Search instead for 
Did you mean: 
maguire
Community Champion

Different behavior when sending Canvas RESTful API request with JSON body versus x-www-form-urlencoded body

I recently tried passing a JSON encoded body in a request to create a section - rather than passing a /x-www-form-urlencoded body. The results were really surprising - rather than creating a section named "DumbleTest" it created a section with the name "II2202 Collaboration Course 2018-08-08 ". The output below shows the requests and replies with the token replaced by "xxx" (I have manually highlighted the key differences):

./create_section_test.py -v 5694 "DumbleTest"
ARGV : ['-v', '5694', 'DumbleTest']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest']
Testing : False
'Wed Aug 8 12:10:06 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 38\r\nContent-Type: application/json\r\n\r\n'
send: b'{"course_section[name]": "DumbleTest"}'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 163
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: {"id":11124,"course_id":5694,"name":"II2202 '
'Collaboration Course '
'2018-08-08","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')
----------------------------------------------------------------------
./create_section_test.py -v 5694 "DumbleTest"
ARGV : ['-v', '5694', 'DumbleTest']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest']
Testing : False
'Wed Aug 8 12:12:25 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 35\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n'
send: b'course_section%5Bname%5D=DumbleTest'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 143
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: '
'{"id":11125,"course_id":5694,"name":"DumbleTest","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')

However, if rather than sending '{"course_section[name]": "DumbleTest"}' one sends '{"course_section": {"name": "DumbleTest2"}}' it works as expected:

./create_section_test.py -v 5694 "DumbleTest2"
ARGV : ['-v', '5694', 'DumbleTest2']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest2']
Testing : False
'Wed Aug 8 12:38:43 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 43\r\nContent-Type: application/json\r\n\r\n'
send: b'{"course_section": {"name": "DumbleTest2"}}'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 144
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: '
'{"id":11126,"course_id":5694,"name":"DumbleTest2","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')

I can understand the logic in the difference in functionality, but do not understand why if the JSON is not considered to have the proper form why it would would make up a name for the section based on today's date.

The code can be found at Difference between JSON arguments: Chip sandbox 

Tags (3)
4 Replies
pklove
Community Champion

Its not really different behaviour.

The JSON that is not correct results in the made up name.  The correct JSON results in the correct name.

The same happens using the url encoded post.  If you put something like course_section[namewrongname] then it does the same as your incorrect JSON.

Also, if you omit the name then it makes up the name.  So in both wrong cases its not seeing a name field and so the default behaviour is to make one up.  course_section[name] is not marked as required.

So, I think the question is really, if you add in unknown parameters, should the API be returning an error?

maguire
Community Champion

pklove, I would expect a HTTP 422 or other error response if the JSON is incorrect, rather than a made up name. Otherwise, I would expect the documentation to say:  course_section[name] - string - The name of the section. If no name is specified, then a name will be created based on an existing section name and today's date.

pklove
Community Champion

They use 422 if the JSON is malformed.  Maybe extra made up parameters could give a 400?

BTW, I just tried a couple of other endpoints (e.g., user creation) and they also quite happily accept extra non-existent fields without throwing an error.

For example, the user endpoint quite happily accepted:

  somegarb[xxname]=Rubbish

maguire
Community Champion

 pklove, Thanks for this information. One can see the reasoning for this in the robustness principle:

"TCP implementations should follow a general principle of robustness:   be conservative in what you do, be liberal in what you accept from   others."

 (Quoted from section 2.10 page 13 of IETF RFC 791 -RFC 761 - DoD standard Transmission Control Protocol )

So it is good that it does not break, but unexpected for me that it made up a name.

In the post "RESTful API Best Practices and Common Pitfalls" (https://medium.com/@schneidenbach/restful-api-best-practices-and-common-pitfalls-7a83ba3763b5 ), Spencer Schneidenbach agrees with you that a poorly formed request should generate a 400 error code. Should the Canvas API give such 400 error codes in the case of PUTs and POSTs when there are fields that are provided, but are not part of the API? From the point of view of robustness, no they should not; but from the principle of least surprise/astonishment they probably should - as having code that "just happens to work" for many years and then one day does not work as expected because the API has been changed to add a new field - which just happens to have the name of the non-existent field that you have been using could be very surprising.

This probably means that one should have a validation tool that checks what is generated against the documentation and highlights differences. There are some testing tools, such as described in the post by Cody Reichert Testing and validating API responses with JSON Schema : Assertible