จัดการข้อยกเว้น Guzzle และรับเนื้อหา HTTP


122

ฉันต้องการจัดการข้อผิดพลาดจาก Guzzle เมื่อเซิร์ฟเวอร์ส่งคืนรหัสสถานะ 4xx และ 5xx ฉันขอแบบนี้:

$client = $this->getGuzzleClient();
$request = $client->post($url, $headers, $value);
try {
    $response = $request->send();
    return $response->getBody();
} catch (\Exception $e) {
    // How can I get the response body?
}

$e->getMessageส่งคืนข้อมูลรหัส แต่ไม่ใช่เนื้อหาของการตอบสนอง HTTP ฉันจะได้รับร่างกายตอบสนองได้อย่างไร?


1
คำถามนี้เกี่ยวข้องกับคำถามนี้stackoverflow.com/questions/17658283/…และคำตอบอาจมีความช่วยเหลือเช่นกัน
Trendfischer

คำตอบ:


84

Guzzle 3.x

ตามเอกสารคุณสามารถตรวจจับประเภทข้อยกเว้นที่เหมาะสม ( ClientErrorResponseExceptionสำหรับข้อผิดพลาด 4xx) และเรียกgetResponse()ใช้เมธอดเพื่อรับอ็อบเจ็กต์ตอบกลับจากนั้นเรียกgetBody()สิ่งนั้น:

use Guzzle\Http\Exception\ClientErrorResponseException;

...

try {
    $response = $request->send();
} catch (ClientErrorResponseException $exception) {
    $responseBody = $exception->getResponse()->getBody(true);
}

การส่งผ่านtrueไปยังgetBodyฟังก์ชันบ่งชี้ว่าคุณต้องการรับส่วนตอบสนองเป็นสตริง Guzzle\Http\EntityBodyมิฉะนั้นคุณจะได้รับมันเป็นตัวอย่างของการเรียน


232

ปากกระบอกปืน 6.x

ตามเอกสารประเภทข้อยกเว้นที่คุณอาจต้องจับคือ:

  • GuzzleHttp\Exception\ClientException สำหรับข้อผิดพลาด 400 ระดับ
  • GuzzleHttp\Exception\ServerException สำหรับข้อผิดพลาด 500 ระดับ
  • GuzzleHttp\Exception\BadResponseException สำหรับทั้งคู่ (มันคือซุปเปอร์คลาสของพวกเขา)

รหัสเพื่อจัดการข้อผิดพลาดดังกล่าวตอนนี้มีลักษณะดังนี้:

$client = new GuzzleHttp\Client;
try {
    $client->get('http://google.com/nosuchpage');    
}
catch (GuzzleHttp\Exception\ClientException $e) {
    $response = $e->getResponse();
    $responseBodyAsString = $response->getBody()->getContents();
}

12
สำหรับฉัน$response->getBody()->getContents()จะส่งคืนสตริงว่างเปล่า จากนั้นฉันก็พบสิ่งนี้ในเอกสาร : \GuzzleHttp\Psr7\str($e->getResponse()) การส่งข้อความตอบกลับเป็น Psr7 String ทำให้ฉันได้รับข้อความแสดงข้อผิดพลาดที่มีรูปแบบสวยงามและสมบูรณ์
Andy Place

3
@AndyPlace หลังจากดู PSR 7 (ซึ่งไม่ได้อ้างอิงโดยส่วนของเอกสารที่ฉันเชื่อมโยงไปในเวลาที่ฉันเขียนคำตอบนี้ แต่ตอนนี้) ฉันไม่ชัดเจนในทันทีว่าทำไมการโทรถึงPsr7\str()มีผลลัพธ์ที่แตกต่างกัน ถึง->getContents(). คุณมีตัวอย่างเล็กน้อยที่แสดงให้เห็นสิ่งนี้ซึ่งอาจช่วยให้ฉันเข้าใจสิ่งนี้และอาจอัปเดตคำตอบนี้
Mark Amery

24
ควรค่าแก่การกล่าวถึงว่า'http_errors' => falseสามารถส่งตัวเลือกในคำขอ Guzzle ซึ่งปิดใช้งานข้อยกเว้นการขว้างปา แล้วคุณจะได้รับร่างกายด้วยว่าสิ่งที่รหัสสถานะคือไม่และคุณสามารถทดสอบรหัสสถานะในกรณีที่จำเป็นด้วย$response->getBody() $response->getStatusCode()
tremby

2
ในฐานะ @AndyPlace $response->getBody()->getContents()ให้สตริงว่างในกรณีเดียวฉันไม่เข้าใจว่าทำไม แต่การใช้\GuzzleHttp\Psr7\str()ส่งคืนการตอบสนอง HTTP ทั้งหมดเป็นสตริงและฉันจะใช้เฉพาะเนื้อหา HTTP ดังที่ได้กล่าวไว้ในเอกสารประกอบสามารถใช้ร่างกายได้โดยการหล่อเป็นสตริง $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB

1
สิ่งนี้ทำเพื่อฉันแม้ว่าฉันจะได้รับ\GuzzleHttp\Exception\RequestExceptionแทนที่ส่งคืน400รหัสสถานะ ลองใช้ {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas

55

แม้ว่าคำตอบข้างต้นจะดี แต่ก็ไม่พบข้อผิดพลาดของเครือข่าย ดังที่ Mark กล่าวไว้ BadResponseException เป็นเพียง super class สำหรับ ClientException และ ServerException แต่ RequestException ยังเป็นคลาสระดับสูงของ BadResponseException RequestException จะไม่เพียง แต่มีข้อผิดพลาด 400 และ 500 เท่านั้น แต่ยังมีข้อผิดพลาดของเครือข่ายและการเปลี่ยนเส้นทางที่ไม่สิ้นสุดด้วย สมมติว่าคุณร้องขอหน้าเว็บด้านล่าง แต่เครือข่ายของคุณกำลังเล่นอยู่และสิ่งที่คุณจับได้คาดว่าจะมี BadResponseException เท่านั้น แอปพลิเคชันของคุณจะเกิดข้อผิดพลาด

จะดีกว่าในกรณีนี้ที่จะคาดหวัง RequestException และตรวจสอบการตอบกลับ

try {
  $client->get('http://123123123.com')
} catch (RequestException $e) {

  // If there are network errors, we need to ensure the application doesn't crash.
  // if $e->hasResponse is not null we can attempt to get the message
  // Otherwise, we'll just pass a network unavailable message.
  if ($e->hasResponse()) {
    $exception = (string) $e->getResponse()->getBody();
    $exception = json_decode($exception);
    return new JsonResponse($exception, $e->getCode());
  } else {
    return new JsonResponse($e->getMessage(), 503);
  }

}

เป็นJsonResponseคลาสจาก Guzzle?
aexl

JsonResponseมาจาก Symfony
เด็กชาย

14

ตั้งแต่ปี 2019 นี่คือสิ่งที่ฉันอธิบายรายละเอียดจากคำตอบด้านบนและเอกสาร Guzzleเพื่อจัดการกับข้อยกเว้นรับเนื้อหาการตอบกลับรหัสสถานะข้อความและรายการตอบกลับที่มีค่าอื่น ๆ ในบางครั้ง

try {
    /**
     * We use Guzzle to make an HTTP request somewhere in the
     * following theMethodMayThrowException().
     */
    $result = theMethodMayThrowException();
} catch (\GuzzleHttp\Exception\RequestException $e) {
    /**
     * Here we actually catch the instance of GuzzleHttp\Psr7\Response
     * (find it in ./vendor/guzzlehttp/psr7/src/Response.php) with all
     * its own and its 'Message' trait's methods. See more explanations below.
     *
     * So you can have: HTTP status code, message, headers and body.
     * Just check the exception object has the response before.
     */
    if ($e->hasResponse()) {
        $response = $e->getResponse();
        var_dump($response->getStatusCode()); // HTTP status code;
        var_dump($response->getReasonPhrase()); // Response message;
        var_dump((string) $response->getBody()); // Body, normally it is JSON;
        var_dump(json_decode((string) $response->getBody())); // Body as the decoded JSON;
        var_dump($response->getHeaders()); // Headers array;
        var_dump($response->hasHeader('Content-Type')); // Is the header presented?
        var_dump($response->getHeader('Content-Type')[0]); // Concrete header value;
    }
}
// process $result etc. ...

voila คุณจะได้รับข้อมูลการตอบกลับในรายการที่แยกออกจากกันอย่างสะดวก

หมายเหตุด้านข้าง:

ด้วยcatchประโยคเราจับคลาสข้อยกเว้นรูท PHP ของห่วงโซ่การสืบทอด \Exceptionเนื่องจากข้อยกเว้นที่กำหนดเองของ Guzzle ขยาย

วิธีนี้อาจเป็นประโยชน์สำหรับกรณีการใช้งานที่ใช้ Guzzle ภายใต้ประทุนเช่นใน Laravel หรือ AWS API PHP SDK ดังนั้นคุณจึงไม่สามารถจับข้อยกเว้น Guzzle ของแท้ได้

ในกรณีนี้คลาสข้อยกเว้นอาจไม่ใช่คลาสที่กล่าวถึงในเอกสาร Guzzle (เช่น GuzzleHttp\Exception\RequestExceptionเป็นข้อยกเว้นรูทสำหรับ Guzzle)

ดังนั้นคุณต้องจับ \Exceptionแทน แต่จำไว้ว่ามันยังคงเป็นอินสแตนซ์คลาสข้อยกเว้นของ Guzzle

แม้ว่าจะใช้ด้วยความระมัดระวัง เครื่องห่อเหล่านั้นอาจทำให้$e->getResponse()ไม่สามารถใช้วิธีการแท้ของวัตถุGuzzle ได้ ในกรณีนี้คุณจะต้องดูซอร์สโค้ดข้อยกเว้นจริงของ Wrapper และค้นหาวิธีรับสถานะข้อความ ฯลฯ แทนที่จะใช้ Guzzle$responseวิธี 's

หากคุณโทรหา Guzzle โดยตรงด้วยตัวคุณเองคุณสามารถจับGuzzleHttp\Exception\RequestExceptionหรือคนอื่น ๆ ที่กล่าวถึงในเอกสารข้อยกเว้นของพวกเขาที่เกี่ยวกับเงื่อนไขกรณีการใช้งานของคุณ


1
คุณไม่ควรเรียกใช้เมธอดบน$responseวัตถุของคุณเมื่อจัดการกับข้อยกเว้นเว้นแต่คุณจะได้ตรวจสอบ$e->hasResponse()มิฉะนั้น$responseอาจเป็นnullและการเรียกเมธอดใด ๆ จะทำให้เกิดข้อผิดพลาดร้ายแรง
วางแผง

@pwaring จริง. ตรงตามที่เอกสารข้อยกเว้นของ Guzzle กล่าว อัปเดตคำตอบ ขอบคุณ.
Valentine Shi

1
... แต่สิ่งนี้ยังคงเป็นปัญหาหลังจากการแก้ไข คุณกำลังจับข้อยกเว้นทั้งหมดไม่ใช่เฉพาะ Guzzle แต่คุณกำลังเรียกหา$e->hasResponseผลลัพธ์ซึ่งแน่นอนว่าไม่มีข้อยกเว้นสำหรับข้อยกเว้นที่ไม่ใช่ Guzzle ดังนั้นหากคุณเพิ่มข้อยกเว้นที่ไม่ใช่ Guzzle ขึ้นมาtheMethodMayThrowException()รหัสนี้จะจับได้พยายามเรียกวิธีที่ไม่มีอยู่จริงและเกิดข้อผิดพลาดเนื่องจากวิธีการที่ไม่มีอยู่จริงซ่อนสาเหตุที่แท้จริงของข้อผิดพลาดได้อย่างมีประสิทธิภาพ ที่ดีกว่าคือจับGuzzleHttp\Exception\RequestExceptionแทนที่จะExceptionหลีกเลี่ยงสิ่งนี้
Mark Amery

1
@MarkAmery ประเด็นของคุณใช้ได้อย่างสมบูรณ์แบบ ขอบคุณ. ฉันอัปเดตเนื้อหาคำตอบ
Valentine Shi

1
@JaberAlNahian ดีใจที่ได้ยิน :) นั่นคือความตั้งใจของฉัน ยินดีต้อนรับเสมอ.
วาเลนไทน์ชิ

4

ถ้าใส่'http_errors' => falseในตัวเลือกคำขอล่อแล้วมันจะหยุดโยนข้อยกเว้นในขณะที่ได้รับ 4xx หรือ 5xx $client->get(url, ['http_errors' => false])ข้อผิดพลาดเช่นนี้ จากนั้นคุณจะแยกวิเคราะห์การตอบสนองไม่ว่าจะตกลงหรือผิดพลาด แต่จะเป็นการตอบกลับ สำหรับข้อมูลเพิ่มเติม


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