.Net HttpWebRequest.GetResponse () ยกข้อยกเว้นเมื่อรหัสสถานะ http 400 (คำขอไม่ถูกต้อง) ถูกส่งคืน


205

ฉันอยู่ในสถานการณ์ที่เมื่อฉันได้รับรหัส HTTP 400 จากเซิร์ฟเวอร์มันเป็นวิธีการทางกฎหมายอย่างสมบูรณ์ของเซิร์ฟเวอร์ที่บอกฉันว่าผิดกับคำขอของฉัน (ใช้ข้อความในเนื้อหาการตอบสนอง HTTP)

อย่างไรก็ตาม. NET HttpWebRequest ทำให้เกิดข้อยกเว้นเมื่อรหัสสถานะคือ 400

ฉันจะจัดการสิ่งนี้ได้อย่างไร สำหรับฉัน 400 ถูกกฎหมายอย่างสมบูรณ์และค่อนข้างเป็นประโยชน์ เนื้อหา HTTP มีข้อมูลสำคัญบางอย่าง แต่ข้อยกเว้นทำให้ฉันออกนอกเส้นทางของฉัน


6
ฉันกำลังประสบกับสิ่งเดียวกัน ฉันส่งข้อเสนอแนะไปยังทีม. NET Framework อย่าลังเลที่จะลงคะแนนให้: connect.microsoft.com/VisualStudio/feedback/details/575075//
Jonas Stawski

คำตอบ:


344

มันจะดีถ้ามีวิธีการปิด "โยนรหัสไม่สำเร็จ" แต่ถ้าคุณจับ WebException อย่างน้อยคุณสามารถใช้การตอบสนอง:

using System;
using System.IO;
using System.Web;
using System.Net;

public class Test
{
    static void Main()
    {
        WebRequest request = WebRequest.Create("http://csharpindepth.com/asd");
        try
        {
            using (WebResponse response = request.GetResponse())
            {
                Console.WriteLine("Won't get here");
            }
        }
        catch (WebException e)
        {
            using (WebResponse response = e.Response)
            {
                HttpWebResponse httpResponse = (HttpWebResponse) response;
                Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
                using (Stream data = response.GetResponseStream())
                using (var reader = new StreamReader(data))
                {
                    string text = reader.ReadToEnd();
                    Console.WriteLine(text);
                }
            }
        }
    }
}

คุณอาจต้องการแค็ปซูล "รับการตอบกลับจากฉันแม้ว่ามันจะไม่ใช่รหัสสำเร็จ" ในวิธีที่แยกต่างหาก (ฉันขอแนะนำให้คุณยังคงโยนหากไม่มีการตอบสนองเช่นถ้าคุณไม่สามารถเชื่อมต่อได้)

หากการตอบสนองข้อผิดพลาดอาจมีขนาดใหญ่ (ซึ่งผิดปกติ) คุณอาจต้องการปรับแต่งHttpWebRequest.DefaultMaximumErrorResponseLengthเพื่อให้แน่ใจว่าคุณได้รับข้อผิดพลาดทั้งหมด


5
เนื้อหาของสตรีมที่ส่งคืนโดย GetResponseStream () ในการตอบกลับที่แนบมากับ WebException เป็นเพียงชื่อของรหัสสถานะ (เช่น "คำขอไม่ถูกต้อง") แทนที่จะตอบกลับโดยเซิร์ฟเวอร์จริง มีวิธีการรับข้อมูลนี้หรือไม่?
ทำเครื่องหมาย Watts

@ MarkWatts: มันควรจะเป็นสิ่งที่ถูกส่งคืนโดยเซิร์ฟเวอร์และได้รับในทุกสถานการณ์ที่ฉันเคยเห็น คุณสามารถทำซ้ำสิ่งนี้ด้วย URL ภายนอกที่เฉพาะเจาะจงหรือไม่ ฉันขอแนะนำให้คุณถามคำถามใหม่ (อ้างอิงจากคำถามนี้) และแสดงสิ่งที่เกิดขึ้น
Jon Skeet

ปรากฎว่ามันทำเช่นนี้ก็ต่อเมื่อความยาวเนื้อหาของการตอบสนองเป็นศูนย์ มันเพิ่มคำอธิบายที่เป็นข้อความของรหัสสถานะ HTTP - 400 เป็นเพียง "คำขอไม่ถูกต้อง" แต่บางรายการก็มีคำอธิบายเพิ่มเติม
Mark Watts

หากใครรู้ว่าเสื้อคลุมที่ดีสำหรับชั้นนี้วางลิงค์ไปที่นี่ System.Net.WebClient ทำงานในลักษณะเดียวกันโดยเรียกใช้ระบบการจัดการข้อยกเว้น
John K

1
@ AnkushJain: ฉันเชื่อว่ามันจะกลับมาตามปกติสำหรับ 2XX; การเปลี่ยนเส้นทางอาจเกิดขึ้นสำหรับ 3XX ขึ้นอยู่กับการกำหนดค่า - ฉันคาดหวังว่าจะมีสิ่งอื่นที่ทำให้เกิดข้อยกเว้น (สำหรับ 4XX เป็นไปได้ว่ามันจะใช้ข้อมูลรับรองความถูกต้องหากมี แต่ยังไม่ได้ใช้)
Jon Skeet

48

ฉันรู้ว่าสิ่งนี้ได้รับคำตอบมานานแล้ว แต่ฉันได้ทำวิธีการขยายหวังว่าจะช่วยคนอื่น ๆ ที่มาที่คำถามนี้

รหัส:

public static class WebRequestExtensions
{
    public static WebResponse GetResponseWithoutException(this WebRequest request)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        try
        {
            return request.GetResponse();
        }
        catch (WebException e)
        {
            if (e.Response == null)
            {
                throw;
            }

            return e.Response;
        }
    }
}

การใช้งาน:

var request = (HttpWebRequest)WebRequest.CreateHttp("http://invalidurl.com");

//... (initialize more fields)

using (var response = (HttpWebResponse)request.GetResponseWithoutException())
{
    Console.WriteLine("I got Http Status Code: {0}", response.StatusCode);
}

2
WebException.Responsenullสามารถและอาจจะเป็น คุณควรสร้างใหม่หากเป็นกรณีนี้
Ian Kemp

@DavidP ฉันเห็นด้วยว่าการใช้งานค่อนข้างสับสนเล็กน้อย แต่สำหรับทุกสิ่งคุณควรจะใช้HttpClientแทนมันสามารถกำหนดค่าได้มากกว่าและฉันเชื่อว่ามันเป็นหนทางแห่งอนาคต
แมทธิว

การตรวจสอบคำขอ == null จำเป็นจริงๆหรือ เนื่องจากนี่เป็นวิธีส่วนขยายการพยายามใช้กับวัตถุที่เป็นโมฆะควรทำให้เกิดข้อยกเว้นการอ้างอิงเป็นโมฆะก่อนที่จะถึงรหัสวิธีการขยาย ...... ใช่ไหม
kwill

@kwill วิธีการขยายเป็นเพียงวิธีการคงที่การตรวจสอบการป้อนข้อมูลที่เหมาะสมควรทำเพื่อหลีกเลี่ยงความสับสนโดยเฉพาะอย่างยิ่งเมื่อพวกเขาไม่ได้ถูกเรียกด้วยไวยากรณ์วิธีการขยาย นอกจากนี้ยังเป็นวิธีการที่สอดคล้องกันที่ดำเนินการโดยทีมงาน. NET: github.com/dotnet/corefx/blob/…
Matthew

1
ขอโทษฉันเข้าใจผิดคำถามที่สองของคุณ ((WebRequest) null).GetResponseWithoutException()ในความเป็นจริงจะไม่ก่อให้เกิดNullReferenceExceptionเพราะมันจะถูกรวบรวมให้เทียบเท่าWebRequestExtensions.GetResponseWithoutException(null)ซึ่งจะไม่ส่งผลในการNullReferenceExceptionดังนั้นจึงจำเป็นต้องมีการตรวจสอบความถูกต้องของอินพุต
Matthew

13

ที่น่าสนใจคือสิ่งHttpWebResponse.GetResponseStream()ที่คุณได้รับจากสิ่งWebException.Responseนั้นไม่เหมือนกับสตรีมการตอบกลับที่คุณจะได้รับจากเซิร์ฟเวอร์ ในสภาพแวดล้อมของเราเรากำลังสูญเสียการตอบสนองของเซิร์ฟเวอร์จริงเมื่อรหัสสถานะ 400 HTTPถูกส่งกลับไปยังลูกค้าโดยใช้HttpWebRequest/HttpWebResponseวัตถุ จากสิ่งที่เราได้เห็นกระแสการตอบสนองที่เกี่ยวข้องกับที่WebException's HttpWebResponseสร้างขึ้นที่ลูกค้าและไม่รวมถึงเนื้อหาการตอบสนองใด ๆ จากเซิร์ฟเวอร์ น่าผิดหวังอย่างมากเนื่องจากเราต้องการส่งข้อความกลับไปยังลูกค้าถึงเหตุผลของคำขอที่ไม่ดี


ฉันใช้เมธอด HEAD และทำให้เกิดข้อยกเว้นนี้ แต่เมื่อใช้ GET จะไม่มีปัญหาใด ๆ ปัญหาเกี่ยวกับวิธี HEAD เป็นอย่างไร
Mohammad Afrashteh

12

ฉันมีปัญหาที่คล้ายกันเมื่อพยายามเชื่อมต่อกับบริการ OAuth2 ของ Google

ฉันลงเอยด้วยการเขียน POST ด้วยตนเองไม่ได้ใช้ WebRequest เช่นนี้:

TcpClient client = new TcpClient("accounts.google.com", 443);
Stream netStream = client.GetStream();
SslStream sslStream = new SslStream(netStream);
sslStream.AuthenticateAsClient("accounts.google.com");

{
    byte[] contentAsBytes = Encoding.ASCII.GetBytes(content.ToString());

    StringBuilder msg = new StringBuilder();
    msg.AppendLine("POST /o/oauth2/token HTTP/1.1");
    msg.AppendLine("Host: accounts.google.com");
    msg.AppendLine("Content-Type: application/x-www-form-urlencoded");
    msg.AppendLine("Content-Length: " + contentAsBytes.Length.ToString());
    msg.AppendLine("");
    Debug.WriteLine("Request");
    Debug.WriteLine(msg.ToString());
    Debug.WriteLine(content.ToString());

    byte[] headerAsBytes = Encoding.ASCII.GetBytes(msg.ToString());
    sslStream.Write(headerAsBytes);
    sslStream.Write(contentAsBytes);
}

Debug.WriteLine("Response");

StreamReader reader = new StreamReader(sslStream);
while (true)
{  // Print the response line by line to the debug stream for inspection.
    string line = reader.ReadLine();
    if (line == null) break;
    Debug.WriteLine(line);
}

การตอบกลับที่เขียนไปยังสตรีมการตอบกลับจะมีข้อความแสดงข้อผิดพลาดเฉพาะที่คุณต้องการ

โดยเฉพาะอย่างยิ่งปัญหาของฉันคือการที่วางขีดเส้นใต้ระหว่างส่วนข้อมูลที่เข้ารหัส URL เมื่อฉันพาพวกเขาออกทุกอย่างทำงานได้ คุณอาจสามารถใช้เทคนิคที่คล้ายกันเพื่อเชื่อมต่อกับบริการของคุณและอ่านข้อความแสดงข้อผิดพลาดที่เกิดขึ้นจริง


2
HttpWebRequest สับสนมาก แม้แต่ซ็อกเก็ตก็ง่ายกว่า (เพราะมันไม่ได้ซ่อนข้อผิดพลาดจากคุณ)
Agent_L

6

ลองนี้ (มันเป็นรหัส VB ​​:-):

Try

Catch exp As WebException
  Dim sResponse As String = New StreamReader(exp.Response.GetResponseStream()).ReadToEnd
End Try

3

ฟังก์ชันส่วนขยายเวอร์ชันอะซิงโครนัส:

    public static async Task<WebResponse> GetResponseAsyncNoEx(this WebRequest request)
    {
        try
        {
            return await request.GetResponseAsync();
        }
        catch(WebException ex)
        {
            return ex.Response;
        }
    }

0

วิธีนี้แก้ไขได้สำหรับฉัน:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
ปัญหา:
localhost ส่งคืนเนื้อหาที่คาดหวัง IP ระยะไกลจะเปลี่ยนเนื้อหา 400 เป็น "คำขอไม่ถูกต้อง" การ
แก้ไข: การ
เพิ่ม<httpErrors existingResponse="PassThrough"></httpErrors>เพื่อweb.config/configuration/system.webServerแก้ไขปัญหานี้ให้ฉัน; ตอนนี้เซิร์ฟเวอร์ทั้งหมด (ในพื้นที่และระยะไกล) คืนค่าเนื้อหาเดียวกันที่แน่นอน (สร้างโดยฉัน) โดยไม่คำนึงถึงที่อยู่ IP และ / หรือรหัส HTTP ที่ฉันส่งคืน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.