Powershell: Invoke-WebRequest should allow body content on DELETE

Created on 18 May 2017  路  4Comments  路  Source: PowerShell/PowerShell

Invoke-WebRequest should allow body content on DELETE, and perhaps any verb.

Whenever I run into an API that accepts data on DELETE, I am hamstrung by the client-side enforcement of which verbs can send a body and which cannot. For example, posting data on DELETE is used by many applications such as tumblr/collins to log reasons server-side. This may or may not be a good use of verbs and APIs, but I can't fix the server. I'm required to reimplement Invoke-WebRequest around System.Net.HttpWebRequest::CreateHttp to get the request in the shape that I need. Allowing the user to send a body with any verb is going to allow some needed flexibility in our world of varying APIs. Having a request fail because Invoke-WebRequest can only query perfect APIs will keep people from using it. Curl is a good example of how an API consumer should be less opinionated because it's a part of an imperfect world. With curl, if I construct an HTTP request that follows the core of HTTP but mismatches verbs, content, headers, etc, that's really my prerogative. If the goal for PowerShell 6.0 is to be amazing in the cloud, it has to be amazing in some rainier clouds.

As far as HTTP standards, body content on DELETE has some discussion here:
http://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request

Thank you for your time. I love love PowerShell.

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      6.0.0-beta
PSEdition                      Core
BuildVersion                   3.0.0.0
CLRVersion
GitCommitId                    v6.0.0-beta.1
OS                             Darwin 16.5.0 Darwin Kernel Version 16.5.0: Fri Mar  3 16:52:33 PST 2017; root:xnu-3789.51.2~3/RELEASE_X86_64
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Area-Cmdlets-Utility Resolution-Answered

Most helpful comment

I believe this was a lack of understanding on my part.

I dug back into this and tried to reproduce it, and I was able to, but resolving it was as simple as setting -ContentType correctly. This worked even when I rolled back to beta.1.

I'll give the details here for anyone else who may come across this.

In my initial naive attempt, you can see the body content is sent in query string format as the "data" content. The application I was hitting, tumblr collins, would have none of this. I have to admit I spent far too long stuck on this and I didn't have the understanding back then to identify the real issue.

Invoke-WebRequest -Method DELETE -Body $Body -uri https://httpbin.org/delete -Headers @{'authorization'='basic thing'}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    StatusCode        : 200                                                                                                                                                                                                                                                   StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "baz=qux&reason=no+reason&foo=bar",
                      "files": {},
                      "form": {},
                      "headers": {
                        "Accept": "*/*",
                        "Authorization": "basic thing",
                        "Connection": "close",
                       ...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Server: meinheld/0.6.1
                    Date: Tue, 03 Oct 2017 14:01:03 GMT
                    Access-Control-Allow-Origin: *
                    Access-Control-Allow-Credentials: true
                    X-Powered-By: Flask
                    X-Processed-...
Forms             :
Headers           : {[Connection, System.String[]], [Server, System.String[]], [Date, System.String[]], [Access-Control-Allow-Origin, System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        :
RawContentLength  : 535

Today as I revisited this I compared the behavior of POST to DELETE. DELETE doesn't set a content type by default. I have no problem with this behavior. In fact, I found this behavior defined in the official documentation.

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-6
If this parameter is omitted and the request method is POST, Invoke-WebRequest sets the content type to application/x-www-form-urlencoded. Otherwise, the content type is not specified in the call.

Here is all it took:

Invoke-WebRequest -Method DELETE -Body $Body -uri https://httpbin.org/delete -Headers @{'authorization'='basic thing'} -ContentType 'application/x-www-form-urlencoded'
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    StatusCode        : 200                                                                                                                                                                                                                                                   StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "",
                      "files": {},
                      "form": {
                        "baz": "qux",
                        "foo": "bar",
                        "reason": "no reason"
                      },
                      "headers": {
                        "Accept": "*/*",
                        "Authorization": "basic thing...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Server: meinheld/0.6.1
                    Date: Tue, 03 Oct 2017 14:13:48 GMT
                    Access-Control-Allow-Origin: *
                    Access-Control-Allow-Credentials: true
                    X-Powered-By: Flask
                    X-Processed-...
Forms             :
Headers           : {[Connection, System.String[]], [Server, System.String[]], [Date, System.String[]], [Access-Control-Allow-Origin, System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        :
RawContentLength  : 628
RelationLink      : {}

I'll try to learn from this. I appreciate your time. I personally have no reason for this request to remain open.

All 4 comments

@JasonRitchie Thanks for your report!

@JasonRitchie is there a specific scenario/api that fails because we don't allow body on DELETE? I'm trying to prioritize this work. Thanks.

@SteveL-MSFT This appears to be fixed (likely due to upstream changes). I can no longer repro this.
@JasonRitchie Can you confirm this is no longer an issue?

$Body = "Test"
Invoke-WebRequest -Method DELETE -Body $Body -uri https://httpbin.org/delete
Invoke-RestMethod -Method DELETE -Body $Body -uri https://httpbin.org/delete

Result:

StatusCode        : 200
StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "Test",
                      "files": {},
                      "form": {},
                      "headers": {
                        "Connection": "close",
                        "Content-Length": "4",
                        "Host": "httpbin.org",
                        "User-Agent": "Mozilla/5.0 (Wi...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Date: Tue, 03 Oct 2017 09:23:03 GMT
                    Via: 1.1 vegur
                    Server: meinheld/0.6.1
                    Access-Control-Allow-Origin: *
                    Access-Control-Allow-Credentials: true
                    X-Powered-...
Forms             :
Headers           : {[Connection, System.String[]], [Date, System.String[]], [Via, System.String[]], [Server,
                    System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        :
RawContentLength  : 360
RelationLink      : {}

args    :
data    : Test
files   :
form    :
headers : @{Connection=close; Content-Length=4; Host=httpbin.org; User-Agent=Mozilla/5.0 (Windows NT; Microsoft
          Windows 10.0.15063 ; en-US) PowerShell/6.0.0}
json    :
origin  : 173.239.232.67
url     : https://httpbin.org/delete

$PSversionTable:

Name                           Value
----                           -----
PSVersion                      6.0.0-beta.7
PSEdition                      Core
GitCommitId                    v6.0.0-beta.7-83-g417e9889399e2e0e6a90d52ff58ae08ec08b1cf2
OS                             Microsoft Windows 10.0.15063
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

I believe this was a lack of understanding on my part.

I dug back into this and tried to reproduce it, and I was able to, but resolving it was as simple as setting -ContentType correctly. This worked even when I rolled back to beta.1.

I'll give the details here for anyone else who may come across this.

In my initial naive attempt, you can see the body content is sent in query string format as the "data" content. The application I was hitting, tumblr collins, would have none of this. I have to admit I spent far too long stuck on this and I didn't have the understanding back then to identify the real issue.

Invoke-WebRequest -Method DELETE -Body $Body -uri https://httpbin.org/delete -Headers @{'authorization'='basic thing'}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    StatusCode        : 200                                                                                                                                                                                                                                                   StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "baz=qux&reason=no+reason&foo=bar",
                      "files": {},
                      "form": {},
                      "headers": {
                        "Accept": "*/*",
                        "Authorization": "basic thing",
                        "Connection": "close",
                       ...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Server: meinheld/0.6.1
                    Date: Tue, 03 Oct 2017 14:01:03 GMT
                    Access-Control-Allow-Origin: *
                    Access-Control-Allow-Credentials: true
                    X-Powered-By: Flask
                    X-Processed-...
Forms             :
Headers           : {[Connection, System.String[]], [Server, System.String[]], [Date, System.String[]], [Access-Control-Allow-Origin, System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        :
RawContentLength  : 535

Today as I revisited this I compared the behavior of POST to DELETE. DELETE doesn't set a content type by default. I have no problem with this behavior. In fact, I found this behavior defined in the official documentation.

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-6
If this parameter is omitted and the request method is POST, Invoke-WebRequest sets the content type to application/x-www-form-urlencoded. Otherwise, the content type is not specified in the call.

Here is all it took:

Invoke-WebRequest -Method DELETE -Body $Body -uri https://httpbin.org/delete -Headers @{'authorization'='basic thing'} -ContentType 'application/x-www-form-urlencoded'
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    StatusCode        : 200                                                                                                                                                                                                                                                   StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "",
                      "files": {},
                      "form": {
                        "baz": "qux",
                        "foo": "bar",
                        "reason": "no reason"
                      },
                      "headers": {
                        "Accept": "*/*",
                        "Authorization": "basic thing...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Server: meinheld/0.6.1
                    Date: Tue, 03 Oct 2017 14:13:48 GMT
                    Access-Control-Allow-Origin: *
                    Access-Control-Allow-Credentials: true
                    X-Powered-By: Flask
                    X-Processed-...
Forms             :
Headers           : {[Connection, System.String[]], [Server, System.String[]], [Date, System.String[]], [Access-Control-Allow-Origin, System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        :
RawContentLength  : 628
RelationLink      : {}

I'll try to learn from this. I appreciate your time. I personally have no reason for this request to remain open.

Was this page helpful?
0 / 5 - 0 ratings