return HTTP response for error status as part of exception#3201
return HTTP response for error status as part of exception#3201daxian-dbw merged 12 commits intoPowerShell:masterfrom
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
| { | ||
| /// <summary> | ||
| /// Exception class for webcmdlets to enable returning HTTP error response | ||
| /// </summary> |
There was a problem hiding this comment.
Should this class be sealed? Or is there a reason to derive from it?
There was a problem hiding this comment.
Agree, no reason to derive from it
|
|
||
| /// <summary> | ||
| /// HTTP error status code | ||
| /// </summary> |
There was a problem hiding this comment.
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.
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> |
There was a problem hiding this comment.
Same for this property. Is this _response.Headers?
|
|
||
| /// <summary> | ||
| /// HTTP error response | ||
| /// </summary> |
There was a problem hiding this comment.
I believe we normally use automatic implemented getter/setter property syntax:
public HttpResponseMessage Response { get; set; }| { | ||
| 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.
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.
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.
Should we test for Exception.Headers and Exception.Status too?
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
We should make the setter private { get; private set; }. Otherwise LGTM.
There was a problem hiding this comment.
I agree. WebException.Response only has a getter.
|
@SteveL-MSFT We have another similar PR here #3089, please take a look at the comments in that PR to see any of them 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? |
| { | ||
| /// <summary> | ||
| /// Exception class for webcmdlets to enable returning HTTP error response | ||
| /// </summary> |
There was a problem hiding this comment.
I suggest we make HttpResponseException derive from HttpRequestException, so that { Invoke-WebRequest <url> } catch [HttpRequestException] { } will continue to work after this change.
| 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.
Will make the change
| $result = ExecuteWebCommand -command $command | ||
| $result.Error | Should BeNullOrEmpty | ||
| } | ||
|
|
There was a problem hiding this comment.
pls add a test with a response content
…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.
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.
| 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.
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.
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.
er.ErrorDetails should be set only if detailMsg is not an empty string..
| $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.
Minor comment: this might fail if we are going to run this test on a localized machine.
There was a problem hiding this comment.
@daxian-dbw Is a match good enough for the part returned from the server?
There was a problem hiding this comment.
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)." |
…lMsg is empty changed error message test to be localization friendly by just matching the part returned from server
| { | ||
| /// <summary> | ||
| /// Exception class for webcmdlets to enable returning HTTP error response | ||
| /// </summary> |
There was a problem hiding this comment.
I suggest moving the class into a separate file
There was a problem hiding this comment.
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.
I suggest using try-catch-finally to avoid nesting
There was a problem hiding this comment.
@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.
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(); } }
| @@ -0,0 +1,610 @@ | |||
| #if CORECLR | |||
|
|
|||
| /********************************************************************++ | |||
There was a problem hiding this comment.
And this file got committed by accident 😄
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.