Ich versuche, auf zwei http-Anforderungsparameter in einem Java-Servlet-Filter zuzugreifen, was hier nichts Neues ist, war aber überrascht, dass die Parameter bereits verwendet wurden! Aus diesem Grund ist es in der Filterkette nicht mehr verfügbar.
Es scheint, dass dies nur auftritt, wenn die Parameter in einem POST-Anforderungshauptteil (z. B. einem Formular zum Senden) eingehen.
Gibt es eine Möglichkeit, die Parameter zu lesen und NICHT zu verbrauchen?
Bisher habe ich nur diese Referenz gefunden: Servlet-Filter mit request.getParameter verliert Formulardaten .
Vielen Dank!
Antworten:
Abgesehen davon besteht eine alternative Möglichkeit zur Lösung dieses Problems darin, nicht die Filterkette zu verwenden und stattdessen eine eigene Interceptor-Komponente zu erstellen, möglicherweise unter Verwendung von Aspekten, die auf dem analysierten Anforderungshauptteil ausgeführt werden können. Dies ist wahrscheinlich auch effizienter, da Sie die Anforderung nur einmal
InputStream
in Ihr eigenes Modellobjekt konvertieren .Ich halte es jedoch immer noch für sinnvoll, den Anforderungshauptteil mehr als einmal lesen zu wollen, insbesondere wenn sich die Anforderung durch die Filterkette bewegt. Normalerweise verwende ich Filterketten für bestimmte Vorgänge, die ich auf der HTTP-Ebene behalten möchte, entkoppelt von den Servicekomponenten.
Wie von Will Hartung vorgeschlagen, habe ich dies erreicht, indem ich
HttpServletRequestWrapper
die Anforderung erweitert , verbrauchtInputStream
und im Wesentlichen die Bytes zwischengespeichert habe.Jetzt kann der Anforderungshauptteil mehr als einmal gelesen werden, indem die ursprüngliche Anforderung umbrochen wird, bevor sie durch die Filterkette geleitet wird:
Mit dieser Lösung können Sie den Anforderungshauptteil auch mehrmals über die
getParameterXXX
Methoden lesen , da der zugrunde liegende Aufruf lautetgetInputStream()
, der natürlich die zwischengespeicherte Anforderung liestInputStream
.Bearbeiten
Für neuere Version der
ServletInputStream
Schnittstelle. Sie müssen wie Implementierung einiger Methoden zur Verfügung zu stellenisReady
,setReadListener
usw. Siehe diese Frage , wie unten in Kommentar versehen.quelle
getInputStream
wird mein Wrapper aufgerufen, da dies dieServletRequest
Instanz ist, die ich in die Filterkette übergebe. Wenn Sie immer noch Zweifel haben, lesen Sie den Quellcode fürServletRequestWrapper
und dieServletRequest
Schnittstelle.isReady()
.isFinished()
undsetReadListener()
sich mit nicht blockierenden E / A zu befassen, die implementiert werden müssen. Ich denke, der ReadListener könnte leer bleiben, bin mir aber nicht sicher, was ich tun sollisFinished()
und / oderisReady()
.Ich weiß, dass ich zu spät komme, aber diese Frage war für mich immer noch relevant und dieser SO-Beitrag war einer der Top-Hits in Google. Ich gehe voran und poste meine Lösung in der Hoffnung, dass jemand anderes ein paar Stunden sparen könnte.
In meinem Fall musste ich alle Anfragen und Antworten mit ihren Körpern protokollieren. Mit Spring Framework ist die Antwort eigentlich ganz einfach. Verwenden Sie einfach ContentCachingRequestWrapper und ContentCachingResponseWrapper .
quelle
requestBody
undresponseBody
waren leere Saitenchain.doFilter(request, response);
statt einerchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
Klassen haben den teuren Preis, den Eingabestream zu verbrauchen, so dass das "Caching" über die Methode erfolgt,getContentAsByteArray
aber diese Klasse speichert den Eingabestream nicht zwischen, der möglicherweise von anderen Filtern in der Filterkette benötigt wird (was mein Anwendungsfall ist). Imho, dies ist ein nicht erwartetes Verhalten einer Content-Caching-Klasse, daher habe ich diese Verbesserung im Frühjahrsteam jira.spring.io/browse/SPR-16028AbstractRequestLoggingFilter
von Spring aus verwenden, wo der größte Teil der Arbeit bereits von Spring erledigt wird und Sie nur 1 oder 2 einfache Methoden überschreiben müssen.spring-web-4.3.12.RELEASE
. Als ich die Quelle überprüfte, stellte ich fest, dass die VariablecachedContent
zum Speichern verschiedener Inhalte wie Anforderungsparameter und Anforderungseingabestream verwendet wird. Es ist leer, wenn Sie nur anrufengetContentAsByteArray()
. Um den Anfragetext zu erhalten, müssen Sie anrufengetInputStream()
. Aber auch dies macht den inputStream für andere Filter und den Handler nicht verfügbar.Die obigen Antworten waren sehr hilfreich, hatten aber meiner Erfahrung nach immer noch einige Probleme. Unter Tomcat 7 Servlet 3.0 mussten auch getParamter und getParamterValues überschrieben werden. Die Lösung umfasst hier sowohl Get-Query-Parameter als auch den Post-Body. Es ermöglicht das einfache Erhalten von Rohstrings.
Wie die anderen Lösungen verwendet es Apache Commons-Io und Googles Guava.
In dieser Lösung lösen die getParameter * -Methoden keine IOException aus, verwenden jedoch super.getInputStream () (um den Body abzurufen), wodurch möglicherweise eine IOException ausgelöst wird. Ich fange es und wirf runtimeException. Es ist nicht so schön.
quelle
decode(getPostBodyAsString(), result);
ingetParameterMap()
? Dadurch wird ein Parameter mit key = request body und value = null erstellt, was ziemlich seltsam ist.super.getParameterMap()
Ihre an, anstatt die gesamte String-Analyse durchzugehengetParameterMap
? Welches gibt Ihnen eine Karte von<String, String[]>
sowieso.Die einzige Möglichkeit wäre, den gesamten Eingabestream selbst im Filter zu verbrauchen, das zu übernehmen, was Sie möchten, und dann einen neuen InputStream für den gelesenen Inhalt zu erstellen und diesen InputStream in einen ServletRequestWrapper (oder HttpServletRequestWrapper) einzufügen.
Der Nachteil ist, dass Sie die Nutzlast selbst analysieren müssen. Der Standard stellt Ihnen diese Funktion nicht zur Verfügung.
Nachträge -
Wie gesagt, Sie müssen sich HttpServletRequestWrapper ansehen.
In einem Filter fahren Sie fort, indem Sie FilterChain.doFilter aufrufen (Anfrage, Antwort).
Bei trivialen Filtern stimmen Anforderung und Antwort mit denen überein, die an den Filter übergeben wurden. Das muss nicht so sein. Sie können diese durch Ihre eigenen Anfragen und / oder Antworten ersetzen.
HttpServletRequestWrapper wurde speziell entwickelt, um dies zu erleichtern. Sie übergeben ihm die ursprüngliche Anfrage und können dann alle Anrufe abfangen. Sie erstellen eine eigene Unterklasse davon und ersetzen die Methode getInputStream durch eine eigene. Sie können den Eingabestream der ursprünglichen Anforderung nicht ändern. Stattdessen haben Sie diesen Wrapper und geben Ihren eigenen Eingabestream zurück.
Der einfachste Fall besteht darin, den ursprünglichen Eingabestream für Anforderungen in einen Bytepuffer zu kopieren, die gewünschte Magie auszuführen und dann aus diesem Puffer einen neuen ByteArrayInputStream zu erstellen. Dies wird in Ihrem Wrapper zurückgegeben, der an die FilterChain.doFilter-Methode übergeben wird.
Sie müssen ServletInputStream in eine Unterklasse unterteilen und einen weiteren Wrapper für Ihren ByteArrayInputStream erstellen, aber das ist auch keine große Sache.
quelle
Ich hatte auch das gleiche Problem und ich glaube, der folgende Code ist einfacher und funktioniert für mich.
in der Filter-Java-Klasse
Bitte lassen Sie mich wissen, wenn Sie Fragen haben
quelle
Dies ist also im Grunde Lathys Antwort, ABER aktualisiert für neuere Anforderungen für ServletInputStream.
Nämlich (für ServletInputStream) muss Folgendes implementiert werden:
Dies ist das bearbeitete Objekt von Lathy
und irgendwo (??) habe ich das gefunden (das ist eine erstklassige Klasse, die sich mit den "zusätzlichen" Methoden befasst.
Letztendlich habe ich nur versucht, die Anfragen zu protokollieren. Und die oben genannten frankensteined zusammen Stücke halfen mir, das unten zu schaffen.
Bitte nehmen Sie diesen Code mit einem Körnchen Salz.
Der wichtigste "Test" ist, ob ein POST mit einer Nutzlast arbeitet. Dies ist es, was "Double Read" -Probleme aufdeckt.
Pseudo-Beispielcode
Sie können "MyCustomObject" durch "Object" ersetzen, wenn Sie nur testen möchten.
Diese Antwort basiert auf verschiedenen SOF-Posts und Beispielen. Es hat jedoch eine Weile gedauert, bis alles zusammengekommen ist. Ich hoffe, es hilft einem zukünftigen Leser.
Bitte stimmen Sie Lathys Antwort vor meiner zu. Ohne sie hätte ich nicht so weit kommen können.
Unten ist eine / einige der Ausnahmen, die ich beim Ausarbeiten bekommen habe.
Es sieht so aus, als wären einige der Orte, von denen ich "geliehen" habe, hier:
http://slackspace.de/articles/log-request-body-with-spring-boot/
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/
https://www.oodlestechnologies.com/blogs/How-to-create-duplicate-object-of-httpServletRequest-object
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
quelle
Der Frühling hat hierfür eine integrierte Unterstützung mit
AbstractRequestLoggingFilter
:Leider können Sie die Nutzdaten immer noch nicht direkt aus der Anforderung lesen, aber der Parameter String message enthält die Nutzdaten, sodass Sie sie von dort aus wie folgt abrufen können:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
quelle
Nur das Überschreiben von
getInputStream()
hat in meinem Fall nicht funktioniert. Meine Serverimplementierung scheint Parameter zu analysieren, ohne diese Methode aufzurufen. Ich habe keinen anderen Weg gefunden, aber auch alle vier getParameter * -Methoden neu implementiert. Hier ist der Code vongetParameterMap
(Apache Http Client und Google Guava Bibliothek verwendet):quelle
Wenn Sie die Kontrolle über die Anforderung haben, können Sie den Inhaltstyp auf binär / Oktett-Stream setzen . Dies ermöglicht das Abfragen von Parametern, ohne den Eingabestream zu verbrauchen.
Dies kann jedoch für einige Anwendungsserver spezifisch sein. Ich habe nur Tomcat getestet. Der Steg scheint sich laut https://stackoverflow.com/a/11434646/957103 genauso zu verhalten .
quelle
Die Methode getContentAsByteArray () der Spring-Klasse ContentCachingRequestWrapper liest den Body mehrmals, aber die Methoden getInputStream () und getReader () derselben Klasse lesen den Body nicht mehrmals:
"Diese Klasse speichert den Anforderungshauptteil zwischen, indem sie den InputStream verwendet. Wenn wir den InputStream in einem der Filter lesen, können andere nachfolgende Filter in der Filterkette ihn nicht mehr lesen. Aufgrund dieser Einschränkung ist diese Klasse nicht für alle geeignet Situationen. "
In meinem Fall bestand eine allgemeinere Lösung, die dieses Problem löste, darin, meinem Spring-Boot-Projekt die folgenden drei Klassen hinzuzufügen (und die erforderlichen Abhängigkeiten zur POM-Datei):
CachedBodyHttpServletRequest.java:
CachedBodyServletInputStream.java:
ContentCachingFilter.java:
Ich habe pom auch die folgenden Abhängigkeiten hinzugefügt:
Eine Anleitung und einen vollständigen Quellcode finden Sie hier: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
quelle
Sie können die Servlet-Filterkette verwenden, aber stattdessen die ursprüngliche verwenden. Sie können Ihre eigene Anfrage erstellen. Ihre eigenen Anfragen erweitern HttpServletRequestWrapper.
quelle
Zunächst sollten wir keine Parameter innerhalb des Filters lesen. Normalerweise werden die Header im Filter gelesen, um nur wenige Authentifizierungsaufgaben auszuführen. Allerdings kann man den HttpRequest-Body mithilfe der CharStreams vollständig im Filter oder Interceptor lesen:
Dies hat keinerlei Auswirkungen auf die nachfolgenden Lesevorgänge.
quelle
request.getReader()
wird ein Reader zurückgegeben, der nur bei den nachfolgenden Lesevorgängen eine leere Zeichenfolge enthält.