-
Notifications
You must be signed in to change notification settings - Fork 7.7k
return HTTP response for error status as part of exception #3201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…xperience to Windows PowerShell added tests for invoke-webrequest and invoke-restmethod for http error cases
…xperience to Windows PowerShell added tests for invoke-webrequest and invoke-restmethod for http error cases fixed bad merge
@@ -20,6 +20,50 @@ | |||
namespace Microsoft.PowerShell.Commands | |||
{ | |||
/// <summary> | |||
/// Exception class for webcmdlets to enable returning HTTP error response | |||
/// </summary> | |||
public class HttpResponseException : Exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this class be sealed? Or is there a reason to derive from it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, no reason to derive from it
/// <summary> | ||
/// HTTP error status code | ||
/// </summary> | ||
public HttpStatusCode Status |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this property is never set in the error handling code below. Does this just return the response.StatusCode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was originally adding the individual properties before I saw the example of Windows PowerShell just having a Response property. Didn't remove them. Will remove.
/// <summary> | ||
/// HTTP error headers | ||
/// </summary> | ||
public HttpResponseHeaders Headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for this property. Is this _response.Headers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will remove.
/// <summary> | ||
/// HTTP error response | ||
/// </summary> | ||
public HttpResponseMessage Response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we normally use automatic implemented getter/setter property syntax:
public HttpResponseMessage Response { get; set; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
{ | ||
string message = String.Format(CultureInfo.CurrentCulture, WebCmdletStrings.ResponseStatusCodeFailure, | ||
response.StatusCode.ToString(), response.ReasonPhrase); | ||
HttpResponseException httpEx = new HttpResponseException(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use this syntax here for creating the HttpResponseException:
var httpEx = new HttpResponseException(message)
{
Response = response,
Status = ...
Headers = ...
};
Or you could make the constructor take all property values and make the properties read only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will change constructor
$result = ExecuteWebCommand -command $command | ||
|
||
$result.Error.Exception | Should BeOfType Microsoft.PowerShell.Commands.HttpResponseException | ||
$result.Error.Exception.Response.StatusCode | Should Be 418 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we test for Exception.Headers and Exception.Status too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are getting removed
…ed Headers and Status members, added response to constuctor based on feedback
@PaulHigin thanks for the feedback, addressed each of them |
/// <summary> | ||
/// HTTP error response | ||
/// </summary> | ||
public HttpResponseMessage Response { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make the setter private { get; private set; }. Otherwise LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. WebException.Response
only has a getter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense
@SteveL-MSFT We have another similar PR here #3089, please take a look at the comments in that PR to see any of th
685C
em apply in this fix as well. |
will the new version of the cmdlet output the content if the code was unsuccessful? I could say in the context of my scope, that was a real issue and we even already rewrote our code to curl instead of powershell whereas the status code was not a case, afaik we were able to parse it using regex:)). UPD: sorry, I found it the response property. But now the problem is, the response now is not disposed properly as the FullClr impl does |
@2xmax you are right, this PR only covers partial of #3089. It doesn't capture the content from the response and put it in @SteveL-MSFT, do you want to address it in this same PR or a different one? |
@@ -20,6 +20,27 @@ | |||
namespace Microsoft.PowerShell.Commands | |||
{ | |||
/// <summary> | |||
/// Exception class for webcmdlets to enable returning HTTP error response | |||
/// </summary> | |||
public sealed class HttpResponseException : Exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we make HttpResponseException
derive from HttpRequestException
, so that { Invoke-WebRequest <url> } catch [HttpRequestException] { }
will continue to work after this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
HttpResponseException httpEx = new HttpResponseException(message, response); | ||
ErrorRecord er = new ErrorRecord(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); | ||
er.ErrorDetails = new ErrorDetails(message); | ||
ThrowTerminatingError(er); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will make the change
@@ -642,6 +654,17 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { | |||
$result = ExecuteWebCommand -command $command | |||
$result.Error | Should BeNullOrEmpty | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls add a test with a response content
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
…hes continue to work added response stripping html tags to ErrorDetails added test to verify response is in ErrorDetails
Addressed feedback. This should also address #2113 |
HttpResponseException httpEx = new HttpResponseException(message, response); | ||
ErrorRecord er = new ErrorRecord(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); | ||
string detailMsg = ""; | ||
using (StreamReader reader = new StreamReader(StreamHelper.GetResponseStream(response))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exceptions may be thrown out in StreamHelper.GetResponseStream(response)
or reader.ReadToEnd()
. We should put the using
block in a try/catch-all block, just like what we did in full ps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
if (!response.IsSuccessStatusCode) | ||
{ | ||
string message = String.Format(CultureInfo.CurrentCulture, WebCmdletStrings.ResponseStatusCodeFailure, | ||
Convert.ToInt32(response.StatusCode).ToString(), response.ReasonPhrase); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convert.ToInt32(response.StatusCode).ToString()
A minor comment: maybe just (int)response.StatusCode
as in HttpResponseMessage.cs?
If we want to avoid boxing the int argument, then it should be ((int)response.StatusCode).ToString()
, but maybe it's an overkill 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with just the cast
// remove HTML tags making it easier to read | ||
detailMsg = System.Text.RegularExpressions.Regex.Replace(reader.ReadToEnd(), "<[^>]*>",""); | ||
} | ||
er.ErrorDetails = new ErrorDetails(detailMsg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
er.ErrorDetails
should be set only if detailMsg
is not an empty string..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
$result.Error.Exception | Should BeOfType Microsoft.PowerShell.Commands.HttpResponseException | ||
$result.Error.Exception.Response.StatusCode | Should Be 418 | ||
$result.Error.Exception.Response.ReasonPhrase | Should Be "I'm a teapot" | ||
$result.Error.Exception.Message | Should Be "Response status code does not indicate success: 418 (I'm a teapot)." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comment: this might fail if we are going to run this test on a localized machine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daxian-dbw Is a match good enough for the part returned from the server?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to Match
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think so, $result.Error.Exception.Message | Should BeLike * 418 (I'm a teapot)
should be good, since they are returned from the Http service, not affected by PS localization.
$result.Error.Exception | Should BeOfType Microsoft.PowerShell.Commands.HttpResponseException | ||
$result.Error.Exception.Response.StatusCode | Should Be 418 | ||
$result.Error.Exception.Response.ReasonPhrase | Should Be "I'm a teapot" | ||
$result.Error.Exception.Message | Should Be "Response status code does not indicate success: 418 (I'm a teapot)." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same minor comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to Match
…lMsg is empty changed error message test to be localization friendly by just matching the part returned from server
@@ -20,6 +20,27 @@ | |||
namespace Microsoft.PowerShell.Commands | |||
{ | |||
/// <summary> | |||
/// Exception class for webcmdlets to enable returning HTTP error response | |||
/// </summary> | |||
public sealed class HttpResponseException : HttpRequestException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest moving the class into a separate file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a search of the existing source and it appears that in some cases Exceptions are declared in a separate file (like SessionStateExceptions.cs), but in many other cases, the Exception is declared where it's used (like parserutils.cs).
HttpResponseException httpEx = new HttpResponseException(message, response); | ||
ErrorRecord er = new ErrorRecord(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); | ||
string detailMsg = ""; | ||
try |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest using try-catch-finally to avoid nesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@2xmax do you mean like this?
StreamReader reader = null;
try
{
reader = new StreamReader(StreamHelper.GetResponseStream(response))
....
}
catch { }
finally { if (reader != null) { reader.Dispose(); } }
… if detailMsg is empty changed error message test to be localization friendly by just matching the part returned from server
HttpResponseException httpEx = new HttpResponseException(message, response); | ||
ErrorRecord er = new ErrorRecord(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); | ||
string detailMsg = ""; | ||
StreamReader reader = new StreamReader(StreamHelper.GetResponseStream(response)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid you need to put new StreamReader(StreamHelper.GetResponseStream(response));
in the try block. Maybe like this:
string detailMsg = "";
StreamReader reader = null;
try
{
reader = new StreamReader(StreamHelper.GetResponseStream(response))
detailMsg = System.Text.RegularExpressions.Regex.Replace(reader.ReadToEnd(), "<[^>]*>","");
}
catch { }
finally { if (reader != null) { reader.Dispose(); } }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -0,0 +1,610 @@ | |||
#if CORECLR | |||
|
|||
/********************************************************************++ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this file got committed by accident 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
moved allocation of StreamReader into try block
Addresses #2193
When Invoke-WebRequest or Invoke-RestMethod receives a HTTS status error code, an exception is returned and the user can't get the HTTP response without additional work. This change makes it more consistent with Windows PowerShell in that a Response property which contains the HttpResponse is a member of the resulting Exception.