8000 return HTTP response for error status as part of exception by SteveL-MSFT · Pull Request #3201 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

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

Merged
merged 12 commits into from
Feb 25, 2017

Conversation

SteveL-MSFT
Copy link
Member

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.

…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
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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; }

Copy link
Member Author

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);
Copy link
Contributor

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.

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

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
@SteveL-MSFT
Copy link
Member Author

@PaulHigin thanks for the feedback, addressed each of them

/// <summary>
/// HTTP error response
/// </summary>
public HttpResponseMessage Response { get; set; }
Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

@daxian-dbw daxian-dbw self-assigned this Feb 24, 2017
@daxian-dbw
Copy link
Member
daxian-dbw commented Feb 24, 2017

@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.
I suggest we have our HttpResponseException derive from System.Net.Http.HttpRequestException, so that try { Invoke-WebRequest <url> } catch [HttpRequestException] { } would continue to work after this change.

@daxian-dbw
Copy link
Member

@2xmax (the author of #3089). would you mind take a look?

@2xmax
Copy link
Contributor
2xmax commented Feb 24, 2017

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

@daxian-dbw
Copy link
Member

@2xmax you are right, this PR only covers partial of #3089. It doesn't capture the content from the response and put it in ErrorDetail as full powershell does (see code here).

@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
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message for ErrorDetails should be the content of the response (after using regex replace to remove tags). This was what #3089 tried to fix, and the corresponding code in full powershell is here

Copy link
Member Author

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
}

Copy link
Contributor

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

Copy link
Member Author

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
@SteveL-MSFT
Copy link
Member Author

Addressed feedback. This should also address #2113

@daxian-dbw daxian-dbw dismissed their stale review February 25, 2017 00:58

new commits pushed

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)))
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

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 😄

Copy link
Member Author

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);
Copy link
Member

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..

Copy link
Member Author

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)."
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to Match

Copy link
Member

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)."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same minor comment here.

Copy link
Member Author

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
Copy link
Contributor

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

Copy link
Member Author

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
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Member
@daxian-dbw daxian-dbw Feb 25, 2017

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(); } }

daxian-dbw
daxian-dbw previously approved these changes Feb 25, 2017
… 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));
Copy link
Member

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(); } }

Copy link
Member Author

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

/********************************************************************++
Copy link
Member

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 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@daxian-dbw daxian-dbw dismissed their stale review February 25, 2017 02:00

new commits were pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0