จะอ่าน Jetty HttpInput (ServletInputStream) หลายครั้งได้อย่างไร

ขณะนี้ฉันกำลังพัฒนา REST API โดยใช้ RestEasy และ Jetty หนึ่งในแผนของฉันกับ REST API นี้คือการสร้างปลั๊กอิน hook เพื่อทำทุกอย่างที่จำเป็นกับคำขอขาเข้าโดยใช้ JAX-RS ContainerRequestFilter สิ่งที่มี ContainerRequestPlugin ใน Jetty คือเมื่อฉันเรียก requestContext.getEntityStream(); ในตัวกรองแล้ว EndPoint Class ของฉันจะไม่สามารถอ่านคำขอได้อีกครั้งแม้ว่าฉันจะตั้งค่า Entity Stream อีกครั้งก็ตาม

ต่อไปนี้เป็นรหัสตัวกรองของฉัน

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

นี่คือคลาสปลายทางของฉัน

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

เมื่อใดก็ตามที่ฉันเรียกวิธีทดสอบนี้ ฉันจะเห็นชื่อที่ส่งในคลาส Filter แต่ในชื่อคลาส Endpoint คือ NULL

หลังจากนั้นฉันก็พบว่า getEntityStream ที่ส่งคืนจาก requestContext คือ Jetty custom ServletInputStream นั่นคือ org.eclipse.jetty.server.HttpInput ฉันเชื่อว่าไม่สามารถอ่านคำขอใน EndPoint ได้เนื่องจากฉันตั้งค่า Entity Stream โดยใช้ ByteArrayInputStream

ดังนั้นคำถามของฉันคือ มีวิธีใดที่จะสร้าง/แปลง Jetty HttpInput โดยใช้การใช้งาน InputStream ทั่วไปหรือไม่ หรือมีวิธีอื่นใดในการแก้ไขกรณีนี้หรือไม่? ฉันจะอ่าน Jetty HttpInput ได้หลายครั้งที่ไหน

ขอขอบคุณและขอแสดงความนับถือ


person yjatip    schedule 12.08.2020    source แหล่งที่มา
comment
คุณอาจพบความช่วยเหลือที่นี่ - คุณอาจต้องบันทึกอินพุตสตรีมไว้ที่ใดที่หนึ่งแล้วเพิ่มใหม่อีกครั้ง เมื่ออ่านเสร็จแล้วเนื่องจากได้รับการออกแบบมาให้อ่านครั้งเดียว   -  person Gryphon    schedule 12.08.2020


คำตอบ (1)


ดังที่คุณสังเกตเห็นอย่างไม่ต้องสงสัย ข้อมูลจำเพาะของ Servlet ไม่อนุญาตให้คุณอ่านเนื้อหาเนื้อหาคำขอสองครั้ง

นี่เป็นการตัดสินใจโดยเจตนา เนื่องจากฟีเจอร์ดังกล่าวจำเป็นต้องมีการแคชหรือการบัฟเฟอร์เนื้อหาเนื้อหาการตอบกลับ ซึ่งนำไปสู่:

  • การโจมตี DoS / Denial of Service ต่างๆ ต่อเว็บแอปของคุณ
  • การหมดเวลาของการไม่ได้ใช้งานในการประมวลผลคำขอ เมื่อโค้ดของคุณอ่านคำขอเป็นครั้งที่สองจากบัฟเฟอร์ และไม่มีการรับส่งข้อมูลเครือข่ายเพื่อรีเซ็ตการหมดเวลาของการไม่ได้ใช้งาน
  • ไม่สามารถได้รับประโยชน์จากหรือใช้การประมวลผล Servlet Async I/O

โดยทั่วไปแล้วจุดสิ้นสุด JAX-RS ต้องการให้สตรีมอินพุต javax.servlet.http.HttpServletRequest ไม่ได้ถูกอ่านเลย ไม่ว่าจะด้วยเหตุผลใดก็ตาม (*)

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

คุณอาจพบวิธีการเฉพาะในการใช้งาน JAX-RS เช่น การใช้โค้ดภายในของ Jersey เพื่อตั้งค่าสตรีมเอนทิตี แต่โค้ดประเภทนั้นจะเปราะบางและอาจส่งผลให้เกิดความจำเป็นในการแก้ไขโค้ดของคุณและคอมไพล์ใหม่ด้วยการอัปเดตไลบรารี Jersey ของคุณ

หากคุณใช้เส้นทางที่กำหนดเอง โปรดใช้ความระมัดระวังเป็นพิเศษที่จะไม่ทำให้เกิดช่องโหว่ที่ชัดเจนในโค้ดของคุณ จำกัดขนาดคำขอของคุณ จำกัดสิ่งที่คุณบัฟเฟอร์ได้ ฯลฯ

โดยทั่วไปแล้ว เว็บแอปที่จำเป็นต้องแก้ไขเนื้อหาสตรีมอินพุตของคำขอจะทำผ่านพร็อกซีเซิร์ฟเล็ตที่ทำการแก้ไขคำขอโดยคนกลางแบบเรียลไทม์ บนพื้นฐานบัฟเฟอร์ต่อบัฟเฟอร์ Jetty มีคลาสแบบนี้เรียกว่าสะดวก AsyncMiddleManServlet โดยพื้นฐานแล้วหมายความว่าไคลเอ็นต์ของคุณพูดคุยกับพร็อกซีที่พูดคุยกับตำแหน่งข้อมูลของคุณ ซึ่งสอดคล้องกับพฤติกรรมของเครือข่ายและความต้องการแรงดันย้อนกลับของเครือข่าย (บางสิ่งที่ตัวกรองบัฟเฟอร์ไม่สามารถจัดการได้อย่างถูกต้อง)

(*) คุณสามารถอ่านเนื้อหา HttpServletRequest โดยไม่ได้ตั้งใจโดยใช้สิ่งต่าง ๆ จากคำขอที่ขอพารามิเตอร์คำขอหรือส่วนของคำขอ (ซึ่งต้องการให้อ่านเนื้อหาเนื้อหาสำหรับประเภทเนื้อหาเฉพาะบางประเภท)

person Joakim Erdfelt    schedule 18.08.2020
comment
ว้าว ขอบคุณมากครับสำหรับคำอธิบายโดยละเอียด! ขอบคุณมากสำหรับข้อมูลของคุณ ฉันเพิ่งรู้ว่านี่เป็นการกระทำโดยเจตนาเพื่อความปลอดภัย จริงๆ แล้ว ฉันจำเป็นต้องบันทึกคำขอและการตอบกลับทั้งหมดลงใน DB และอาจในอนาคตฉันจะต้องแก้ไขเนื้อหาคำขอ/การตอบกลับ มีวิธีปฏิบัติที่ดีที่สุดในการบันทึกคำขอและการตอบกลับ (แทรกลงใน DB) โดยใช้ JAX-RS และ Jetty นอกเหนือจากการใช้ ContainerRequestFilter หรือไม่ ขอบคุณอีกครั้ง - person yjatip; 26.08.2020
comment
ใช้ Jetty Interceptors (อินพุตและเอาต์พุต) เพื่อจับเนื้อหาคำขอหรือเนื้อหาการตอบสนอง คุณยังสามารถแทรกก่อน/หลังสิ่งต่างๆ เช่น การบีบอัด gzip เพื่อดูว่าเนื้อหาจะเป็นอย่างไรโดยไม่มีการบีบอัด ด้วยวิธีนี้คุณสามารถจับกระแสข้อมูลดิบของไบต์และ servlet API จะไม่เกี่ยวข้องด้วยซ้ำ (หรือรู้เกี่ยวกับการมีอยู่ของตัวดัก) - person Joakim Erdfelt; 26.08.2020
comment
ขอบคุณมาก คุณช่วยฉันจริงๆ ท่าน! ;) - person yjatip; 26.08.2020