Bagaimana cara membaca Jetty HttpInput (ServletInputStream) berkali-kali?

Saat ini saya sedang mengembangkan REST API menggunakan RestEasy dan Jetty. Salah satu rencana saya dengan REST API ini adalah membuat plugin hook untuk melakukan apa pun yang diperlukan dengan permintaan masuk menggunakan JAX-RS ContainerRequestFilter . Masalahnya dengan ContainerRequestPlugin di Jetty di sini adalah setelah saya memanggil requestContext.getEntityStream(); di Filter maka permintaan tersebut tidak akan dapat dibaca lagi oleh Kelas EndPoint saya meskipun saya telah mengatur Aliran Entitas lagi.

Berikut ini adalah kode Filter saya

@Provider
@Priority(2000)
public class DummyRequestFilter implements ContainerRequestFilter{
    static Logger log = Logger.getLogger(DummyRequestFilter .class.getName());
    
    @Context
    private HttpServletRequest servletRequest;
    
    @Override
    public void filter(ContainerRequestContext requestContext) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
    String requestBody = "";
    
    try {           
        IOUtils.copy(requestContext.getEntityStream(), baos);
        
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
        
        requestBody = IOUtils.toString(is1);
        
        log.info(requestBody);
        
        requestContext.setEntityStream(is2);
                
    }catch (Exception e) {
        log.log(Level.SEVERE,"Exception Occurred",e);
    }
    }   
}

Lalu inilah kelas titik akhir saya

@Path("/")
public class DummyService {
    
    Logger log = Logger.getLogger(DummyService .class.getName());
    
    @GET
    @Path("test")
    @Produces(MediaType.APPLICATION_JSON)
    public Response test(@FormParam("name") String name) {
        log.info("Name = "+name);

        return Response.status(200).build();
    }
}

Setiap kali saya memanggil metode pengujian ini saya dapat melihat nama yang dikirim di kelas Filter tetapi di kelas Endpoint, nama adalah NULL.

Kemudian saya menemukan bahwa getEntityStream yang dikembalikan dari requestContext adalah Jetty custom ServletInputStream yaitu org.eclipse.jetty.server.HttpInput. Saya yakin permintaan tersebut tidak dapat dibaca di EndPoint karena saya mengatur Aliran Entitas menggunakan ByteArrayInputStream.

Jadi pertanyaan saya adalah, apakah ada cara untuk membangun/mengonversi Jetty HttpInput menggunakan implementasi InputStream generik? atau ada cara lain untuk menyiasati kasus ini? di mana saya bisa membaca Jetty HttpInput berkali-kali?

Terima kasih & Salam


person yjatip    schedule 12.08.2020    source sumber
comment
Anda mungkin menemukan bantuan di sini - Anda mungkin perlu menyimpan aliran input di suatu tempat dan menambahkannya kembali setelah selesai membaca, karena dirancang untuk dibaca satu kali.   -  person Gryphon    schedule 12.08.2020


Jawaban (1)


Seperti yang pasti sudah Anda ketahui, spesifikasi Servlet tidak mengizinkan Anda membaca isi isi Permintaan dua kali.

Ini adalah keputusan yang disengaja karena fitur tersebut memerlukan caching atau buffering konten isi respons. Yang mengarah ke:

  • Berbagai serangan DoS / Denial of Service terhadap aplikasi web Anda.
  • Batas Waktu Idle pada pemrosesan permintaan ketika kode Anda membaca permintaan untuk kedua kalinya dari buffer dan tidak menghasilkan lalu lintas jaringan untuk menyetel ulang batas waktu idle.
  • Ketidakmampuan untuk memanfaatkan atau menggunakan pemrosesan I/O Servlet Async.

Titik akhir JAX-RS biasanya mengharuskan aliran input javax.servlet.http.HttpServletRequest belum dibaca sama sekali karena alasan apa pun (*).

Kode Anda tidak berusaha membatasi ukuran array byte yang Anda alokasikan, akan mudah untuk menyalahgunakan layanan Anda dengan Bom Zip. (contoh: mengirim 42 kilobyte data yang di-unpack menjadi 3,99 petabyte)

Anda mungkin menemukan cara implementasi JAX-RS yang spesifik, seperti menggunakan kode internal Jersey untuk mengatur aliran entitas, namun kode semacam itu akan rapuh dan kemungkinan besar mengakibatkan perlunya memperbaiki kode Anda dan mengkompilasi ulang dengan pembaruan pada perpustakaan Jersey Anda.

Jika Anda menggunakan rute khusus, harap berhati-hati untuk tidak menimbulkan kerentanan yang jelas dalam kode Anda, membatasi ukuran permintaan Anda, membatasi apa yang dapat Anda buffer, dll.

Biasanya aplikasi web yang perlu memodifikasi konten aliran masukan permintaan melakukannya melalui servlet proxy yang melakukan modifikasi perantara terhadap permintaan secara real-time, berdasarkan buffer demi buffer. Jetty memiliki kelas seperti itu, yang disebut AsyncMiddleManServlet. Ini pada dasarnya berarti klien Anda berbicara dengan proxy yang berbicara dengan titik akhir Anda, yang menghormati perilaku jaringan dan kebutuhan tekanan balik jaringan. (sesuatu yang filter buffering tidak dapat tangani dengan benar)

(*) Anda dapat secara tidak sengaja membaca isi HttpServletRequest dengan menggunakan sesuatu dari permintaan yang meminta parameter permintaan atau bagian permintaan (yang mengharuskan isi isi dibaca untuk Tipe Konten tertentu)

person Joakim Erdfelt    schedule 18.08.2020
comment
Wah terima kasih banyak Pak atas penjelasan detailnya!, sangat menghargai masukannya. Saya baru menyadari bahwa ini sengaja dilakukan demi alasan keamanan. Sebenarnya saya perlu mencatat semua permintaan dan tanggapan ke dalam DB dan mungkin di masa depan saya perlu memodifikasi isi permintaan/tanggapan. Ngomong-ngomong, apakah ada praktik terbaik untuk mencatat permintaan dan respons (memasukkan ke dalam DB) menggunakan JAX-RS dan Jetty selain menggunakan ContainerRequestFilter? sekali lagi terima kasih - person yjatip; 26.08.2020
comment
Implementasikan Jetty Interceptors (input dan output) untuk menangkap isi permintaan atau isi respons, Anda bahkan dapat memasukkannya sebelum/sesudah hal-hal seperti kompresi gzip untuk melihat seperti apa isi tanpa kompresi. Dengan cara itu Anda dapat menangkap aliran byte mentah, dan API servlet bahkan tidak terlibat (atau mengetahui keberadaan interseptor) - person Joakim Erdfelt; 26.08.2020
comment
Terima kasih banyak, Anda benar-benar menyelamatkan saya, Pak! ;) - person yjatip; 26.08.2020