Tutorial Pasukan Lucee MY

cfhttp timeout dalam CFML: apa yang Lucee pulangkan bila permintaan gagal

Diukur pada Lucee 7.1.1.17: timeout cfhttp pulangkan 408 dan badan Connection Timeout tanpa pengecualian, dan throwOnError menukar kegagalan itu jadi ralat.

cfhttp timeout dalam CFML: apa yang Lucee pulangkan bila permintaan gagal
0
Kandungan

cfhttp timeout dalam CFML: apa yang Lucee pulangkan bila permintaan gagal

Jawapan pantas: Apabila permintaan cfhttp tamat masa, Lucee tidak membaling pengecualian secara lalai. Ia memulangkan struct hasil dengan statusCode bernilai 408 Request Time-out dan fileContent bernilai teks Connection Timeout, jadi kod yang menyimpan fileContent akan menyimpan mesej enjin itu sebagai jawapan pelayan jauh. Sambungan yang ditolak dilaporkan sebagai Connection Failure. Status code unavailable. dengan error=true, dan throwOnError=true menukar kedua-duanya menjadi pengecualian.

Semua angka di bawah ialah keluaran sebenar rig Lucee 7.1.1.17-RC, dan fakta atribut tag dipetik daripada dokumentasi rasmi.

Apa sebenarnya cfhttp pulangkan apabila permintaan tamat masa?

Bukan pengecualian. Permintaan dengan timeout="1" ke sasaran yang tidur lima saat habis dalam 1001 milisaat dan memulangkan struct biasa:

statusCode : 408 Request Time-out
error      : true
fileContent: Connection Timeout
mimetype   : Unable to determine MIME type of file.
errorDetail: rentetan kosong, bukan struct

408 ialah kod yang dipulangkan oleh enjin, bukan kod daripada pelayan jauh. Jadi semakan statusCode EQ 200 selamat, tetapi semakan len(result.fileContent) akan menerima lapan belas aksara Connection Timeout sebagai kandungan yang sah.

errorDetail pada kes ini rentetan kosong, bukan struct. Menulis result.errorDetail.Message terus akan membaling ralat sendiri; itu berlaku kepada saya semasa membina skrip ujian artikel ini.

Had masa dihormati dengan tepat. Dengan timeout="1", permintaan berhenti pada 1001 milisaat walaupun sasaran memerlukan empat saat. Dengan timeout=createTimeSpan(0,0,0,2), ia berhenti pada 2003 milisaat.

Bila cfhttp membaling pengecualian dan bila ia senyap?

Jadual ini membandingkan lima keadaan yang saya jalankan satu per satu:

KeadaanTanpa throwOnErrorDengan throwOnError=true
200 OKstatusCode 200, error=falseSama, tiada pengecualian
404404 Not Found, error=truePengecualian application, mesej 404 Not Found
503503 Service Unavailable, error=truePengecualian application, mesej 503 Service Unavailable
Tamat masa408 Request Time-out, error=truePengecualian application, mesej 408 Request Time-out
Sambungan ditolakConnection Failure. Status code unavailable.Pengecualian java.lang.Exception

Corak yang perlu diingat: pengecualian untuk kod status membawa type="application" dan detail berbentuk GET <url>, manakala kegagalan sambungan membawa pengecualian Java mentah yang mesejnya menyebut HttpHostConnectException. Dua jenis itu memerlukan blok cfcatch yang berbeza jika anda mahu mesej log yang berguna.

Kegagalan sambungan juga senyap secara lalai. http://127.0.0.1:9/ yang menolak sambungan memulangkan teks status itu dalam tiga milisaat, dan hos yang tidak wujud memberi jawapan yang sama dalam tiga milisaat. val() ke atas kedua-duanya memberi sifar, jadi perbandingan dengan 200 gagal dengan selamat. Cara mengendalikan pengecualian tag lain ada dalam panduan cftry dan cfcatch, dan kegagalan sijil yang juga muncul sebagai kegagalan sambungan dibincangkan dalam artikel ralat SSL truststore.

Lapisan mana yang tamat dahulu: timeout, connectionTimeout atau socketTimeout?

Dokumentasi menyatakan timeout ialah nilai sandaran untuk dua atribut lain: apabila connectionTimeout atau socketTimeout tidak dinyatakan, nilai timeout digunakan untuk kedua-duanya. Jika tiada had masa ditetapkan, Lucee menggunakan baki masa permintaan halaman, dan permintaan sentiasa tamat pada atau sebelum had masa halaman, mengikut mana yang lebih kecil.

Perbezaan itu muncul dalam hasil. Saya menguji 192.0.2.1, blok alamat yang dikhaskan untuk dokumentasi dan tidak sepatutnya dihalakan:

  • Dengan timeout="3" sahaja: 408 Request Time-out selepas 3002 milisaat.
  • Dengan timeout="5" dan connectionTimeout="2": Connection Failure. Status code unavailable. selepas 2004 milisaat.

Jadi lapisan yang tamat menentukan cara kegagalan dilaporkan. Apabila connectionTimeout tamat, Lucee menganggap sambungan tidak dapat dibina. Apabila had keseluruhan tamat, ia melaporkan 408. Untuk API pihak ketiga, tetapkan kedua-dua nilai secara eksplisit: connectionTimeout pendek menghalang permintaan menunggu hos yang mati, manakala socketTimeout lebih panjang memberi ruang kepada pelayan yang lambat menjawab.

Bagaimana menulis gelung cubaan semula yang mengira masanya?

Fungsi di bawah tidak pernah membaling. Ia mengelaskan hasil, cuba semula hanya untuk keadaan yang selamat dicuba, dan menggandakan jeda antara cubaan. Tag cfhttp tiada atribut cubaan semula dalam senarai atributnya, jadi gelung ini ditulis sendiri:

<cfscript>
    function satuCubaan(sasaran, saat) {
        var h = { kod: 0, status: "", rangkaian: false, tamatMasa: false, ms: 0 };
        var mula = getTickCount();
        try {
            cfhttp(url=sasaran, method="GET", timeout=saat, result="jawapan");
            h.status = jawapan.statusCode;
            h.kod = val(jawapan.statusCode);
            h.tamatMasa = (h.kod EQ 408);
            h.rangkaian = findNoCase("Connection Failure", h.status) GT 0;
        } catch (any e) {
            h.status = "PENGECUALIAN " & e.type;
            h.rangkaian = true;
        }
        h.ms = getTickCount() - mula;
        return h;
    }

    function patutCuba(h) {
        return (h.rangkaian OR h.tamatMasa OR h.kod EQ 429 OR (h.kod GTE 500 AND h.kod LTE 599));
    }

    function cubaSemula(sasaran, saat, maks, jeda) {
        var h = {};
        var log = [];
        var mula = getTickCount();
        var n = 0;
        while (n LT maks) {
            n = n + 1;
            h = satuCubaan(sasaran, saat);
            arrayAppend(log, n & ":" & h.kod & "/" & h.ms & "ms");
            if (!patutCuba(h)) {
                break;
            }
            if (n LT maks) {
                sleep(jeda);
                jeda = jeda * 2;
            }
        }
        h.jumlahMs = getTickCount() - mula;
        h.log = arrayToList(log, " | ");
        return h;
    }

    cepat = cubaSemula("https://httpbin.org/get", 5, 3, 500);
    lambat = cubaSemula("https://httpbin.org/delay/4", 1, 3, 500);
</cfscript>
<cfcontent type="text/plain; charset=utf-8">
<cfoutput>cepat: #cepat.log# | jumlah #cepat.jumlahMs# ms
lambat: #lambat.log# | jumlah #lambat.jumlahMs# ms | status #lambat.status#</cfoutput>

Keluarannya pada rig:

cepat: 1:200/267ms | jumlah 267 ms
lambat: 1:408/1002ms | 2:408/1002ms | 3:408/1002ms | jumlah 4507 ms | status 408 Request Time-out

Angka 4507 milisaat itu kos sebenar corak ini: tiga cubaan satu saat ditambah jeda 500 dan 1000 milisaat. Bajet terburuk boleh dikira sebelum menulis kod, iaitu bilangan cubaan darab had masa setiap cubaan, ditambah jumlah jeda. Kalau bajet itu lebih besar daripada had masa halaman, cubaan terakhir tidak sempat bermula. Cubaan semula ke atas 429 tanpa membaca Retry-After hanya menambah beban; had kadar dalam aplikasi sendiri dibincangkan dalam artikel rate limiting dengan cache.

Kenapa POST tidak boleh dicuba semula tanpa kunci idempotensi?

Kerana pelayan tidak tahu dua permintaan itu permintaan yang sama. GET selamat dicuba semula kerana ia tidak mengubah apa-apa. POST yang mencipta pesanan atau memotong stok akan melakukannya dua kali apabila rangkaian gagal selepas pelayan memproses permintaan tetapi sebelum jawapan sampai kepada anda, dan kegagalan itu kelihatan sama seperti kegagalan sebelum pemprosesan.

Jalan keluarnya ialah kunci idempotensi: satu pengecam unik bagi setiap operasi, dihantar sebagai header, dan digunakan oleh pelayan untuk mengesan permintaan berulang. Blok ini menghantar kunci yang sama dua kali dan membaca semula apa yang pelayan terima:

<cfscript>
    function hantar(sasaran, kunci, jumlah) {
        var h = { status: "", kunci: "", jumlah: "" };
        var gema = {};
        try {
            cfhttp(url=sasaran, method="POST", timeout=10, result="jawapan") {
                cfhttpparam(type="header", name="Idempotency-Key", value=kunci);
                cfhttpparam(type="formfield", name="jumlah", value=jumlah);
            }
            h.status = jawapan.statusCode;
            gema = deserializeJSON(jawapan.fileContent);
            h.kunci = gema.headers["Idempotency-Key"];
            h.jumlah = gema.form.jumlah;
        } catch (any e) {
            h.status = "PENGECUALIAN " & e.type;
        }
        return h;
    }

    kunci = createUUID();
    pertama = hantar("https://httpbin.org/post", kunci, "120.00");
    kedua = hantar("https://httpbin.org/post", kunci, "120.00");
</cfscript>
<cfcontent type="text/plain; charset=utf-8">
<cfoutput>status #pertama.status# | kunci #kunci# | gema #pertama.kunci# dan #kedua.kunci# | jumlah #kedua.jumlah#</cfoutput>

Keluarannya mengesahkan header sampai ke pelayan tanpa berubah:

status 200 OK | kunci 0C24BF60-2AF2-4901-BAD48CC29EEA08C6 | gema 0C24BF60-2AF2-4901-BAD48CC29EEA08C6 dan 0C24BF60-2AF2-4901-BAD48CC29EEA08C6 | jumlah 120.00

createUUID() dalam Lucee ini menghasilkan 35 aksara dalam bentuk 8-4-4-16, bukan 36 aksara, jadi pelayan yang menyemak panjang 36 akan menolak kunci ini walaupun ia unik. Pihak pelayan mesti menyimpan kunci bersama hasil pemprosesan supaya permintaan kedua memulangkan hasil yang sama. Bentuk header itu masih draf di IETF, jadi semak kontrak API anda.

Bagaimana mengendalikan header Retry-After?

Pelayan yang sibuk boleh memberitahu berapa lama anda patut menunggu. Nilainya ada dalam responseHeader dan boleh dibaca terus:

<cfscript>
    function bacaRetryAfter(jawapan) {
        var saat = 0;
        for (k in structKeyArray(jawapan.responseHeader)) {
            if (lcase(k) EQ "retry-after") {
                saat = val(jawapan.responseHeader[k]);
            }
        }
        return saat;
    }

    cfhttp(url="https://httpbin.org/response-headers?Retry-After=2", method="GET", timeout=5, result="jawapan");
    saat = bacaRetryAfter(jawapan);
    tidur = min(saat * 1000, 3000);
</cfscript>
<cfcontent type="text/plain; charset=utf-8">
<cfoutput>status #jawapan.statusCode# | Retry-After #saat# saat | tidur #tidur# ms</cfoutput>

Keluarannya status 200 OK | Retry-After 2 saat | tidur 2000 ms. Had atas 3000 milisaat penting kerana pelawat halaman tidak boleh diminta menunggu selama yang pelayan mahu. Jangan andaikan header itu sentiasa ada: satu jawapan 503 daripada sasaran ujian lain dalam sesi yang sama tidak membawa Retry-After langsung, jadi nilai lalai mesti ditetapkan sendiri.

Adakah cfhttp mengikut lencongan secara lalai?

Ya. Dokumentasi menyatakan redirect bernilai benar secara lalai dan mengikut sehingga lima belas lencongan, dan jika had itu dilepasi ia berkelakuan seolah-olah nilainya palsu. Sejak Lucee 7.0.0.208, nilai Lax memulangkan fileContent untuk lencongan bagi permintaan POST dan DELETE.

Dalam rig, lencongan sekali memberi 200 OK dengan badan halaman akhir dan tiada header Location. Dengan redirect="false", hasilnya 302 Found, Location bernilai /get, dan fileContent mengandungi halaman HTML "Redirecting..." kepunyaan pelayan itu. Kod yang menyimpan fileContent tanpa memeriksa kod status boleh menyimpan halaman lencongan sebagai data. Asas tag ini untuk perkhidmatan luar ada dalam panduan cfhttp ke API luar.

Jadual ringkasan

statusCode diterimaMaksudTindakan
200 OKBerjayaBaca fileContent
4xx lainPermintaan salahJangan ulang
429Terlalu banyak permintaanTunggu Retry-After
5xxPelayan gagalUlang dengan jeda berganda
408Had masa enjinUlang dalam bajet halaman
Connection FailureTiada jawapanUlang, bezakan ralat sijil

Soalan Lazim

Adakah cfhttp membaling ralat apabila permintaan tamat masa? Tidak, melainkan throwOnError="true". Tanpa atribut itu, hasilnya struct dengan statusCode 408 Request Time-out, error=true, dan fileContent berbentuk teks Connection Timeout.

Kenapa fileContent saya mengandungi perkataan Connection Timeout? Kerana tiada badan jawapan diterima daripada pelayan jauh, dan Lucee mengisi medan itu dengan mesejnya sendiri. Jangan simpan fileContent sebelum menyemak statusCode, kerana teks itu akan masuk ke cache sebagai data sebenar.

Apa beza connectionTimeout dengan socketTimeout? connectionTimeout mengehadkan masa membina sambungan TCP. socketTimeout mengehadkan masa menunggu data antara paket selepas sambungan terbina. Jika kedua-duanya tidak ditetapkan, nilai timeout digunakan untuk kedua-duanya.

Berapa lama saya boleh cuba semula sebelum halaman tamat masa? Kira dahulu: bilangan cubaan darab had masa setiap cubaan, ditambah jeda. Tiga cubaan dengan had satu saat dan jeda 500 dan 1000 milisaat mengambil 4507 milisaat dalam ujian ini. Dokumentasi menyatakan permintaan tamat pada atau sebelum had masa halaman.

Bolehkah saya cuba semula POST apabila rangkaian gagal? Hanya jika operasi itu idempotensi, atau jika pelayan menyimpan kunci idempotensi anda. Tanpa itu, cubaan kedua boleh mencipta dua pesanan untuk satu tindakan pelawat.

Sumber

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk