Wenn wir uns ansehen, was das OP versucht, versucht er / sie, zwei (möglicherweise nicht verwandte) JSON-Objekte zu veröffentlichen. Erstens ist jede Lösung, um zu versuchen, einen Teil als Körper und einen Teil als einen anderen Parameter, IMO, zu senden, schreckliche Lösungen. POST-Daten sollten in den Körper gelangen. Es ist nicht richtig, etwas zu tun, nur weil es funktioniert. Einige Workarounds verstoßen möglicherweise gegen grundlegende REST-Prinzipien.
Ich sehe ein paar Lösungen
- Verwenden Sie application / x-www-form-urlencoded
- Verwenden Sie Multipart
- Wickeln Sie sie einfach in ein einzelnes übergeordnetes Objekt
1. Verwenden Sie application / x-www-form-urlencoded
Eine andere Möglichkeit ist, nur zu verwenden application/x-www-form-urlencoded
. Wir können tatsächlich JSON-Werte haben. Zum Beispiel
curl -v http:
-d 'one={"modelOne":"helloone"}' \
-d 'two={"modelTwo":"hellotwo"}'
public class ModelOne {
public String modelOne;
}
public class ModelTwo {
public String modelTwo;
}
@Path("model")
public class ModelResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String post(@FormParam("one") ModelOne modelOne,
@FormParam("two") ModelTwo modelTwo) {
return modelOne.modelOne + ":" + modelTwo.modelTwo;
}
}
Das einzige, was wir brauchen, um dies zum Laufen zu bringen, ist ParamConverterProvider
, dass dies funktioniert. Unten ist eine, die von Michal Gadjos vom Jersey Team implementiert wurde ( hier mit Erklärung ).
@Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {
@Context
private Providers providers;
@Override
public <T> ParamConverter<T> getConverter(final Class<T> rawType,
final Type genericType,
final Annotation[] annotations) {
final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
if (mbr == null
|| !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
return null;
}
final ContextResolver<ObjectMapper> contextResolver = providers
.getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
final ObjectMapper mapper = contextResolver != null ?
contextResolver.getContext(rawType) : new ObjectMapper();
return new ParamConverter<T>() {
@Override
public T fromString(final String value) {
try {
return mapper.reader(rawType).readValue(value);
} catch (IOException e) {
throw new ProcessingException(e);
}
}
@Override
public String toString(final T value) {
try {
return mapper.writer().writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new ProcessingException(e);
}
}
};
}
}
Wenn Sie nicht nach Ressourcen und Anbietern suchen, registrieren Sie einfach diesen Anbieter. Das obige Beispiel sollte funktionieren.
2. Verwenden Sie Multipart
Eine Lösung, die niemand erwähnt hat, ist die Verwendung von mehrteiligen . Dies ermöglicht es uns, beliebige Teile in einer Anfrage zu senden. Da jede Anforderung nur einen Entitätstext haben kann, ist Multipart die Problemumgehung, da verschiedene Teile (mit eigenen Inhaltstypen) als Teil des Entitätstexts verwendet werden können.
Hier ist ein Beispiel mit Jersey (siehe offizielles Dokument hier )
Abhängigkeit
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Registrieren Sie die MultipartFeature
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
@ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("stackoverflow.jersey");
register(MultiPartFeature.class);
}
}
Ressourcenklasse
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;
@Path("foobar")
public class MultipartResource {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postFooBar(@FormDataParam("foo") Foo foo,
@FormDataParam("bar") Bar bar) {
String response = foo.foo + "; " + bar.bar;
return Response.ok(response).build();
}
public static class Foo {
public String foo;
}
public static class Bar {
public String bar;
}
}
Nun ist der schwierige Teil bei einigen Kunden, dass es keine Möglichkeit gibt, die Content-Type
einzelnen Körperteile festzulegen, die erforderlich sind, damit die oben genannten Funktionen funktionieren. Der mehrteilige Anbieter sucht nach dem Nachrichtentextleser, basierend auf dem Typ jedes Teils. Wenn es nicht auf application/json
oder einen Typ eingestellt ist, für den Foo
oder Bar
ein Reader vorhanden ist, schlägt dies fehl. Wir werden hier JSON verwenden. Es gibt keine zusätzliche Konfiguration, als einen Reader zur Verfügung zu haben. Ich werde Jackson benutzen. Mit der folgenden Abhängigkeit sollte keine andere Konfiguration erforderlich sein, da der Anbieter durch das Scannen von Klassenpfaden erkannt wird.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Nun der Test. Ich werde cURL verwenden . Sie sehen, ich setze das explizit Content-Type
für jedes Teil mit type
. Das -F
bedeutet zu einem anderen Teil. (Eine Vorstellung davon, wie der Anfragetext tatsächlich aussieht, finden Sie ganz unten im Beitrag.)
curl -v -X POST \
-H "Content-Type:multipart/form-data" \
-F "bar={\"bar\":\"BarBar\"};type=application/json" \
-F "foo={\"foo\":\"FooFoo\"};type=application/json" \
http://localhost:8080/api/foobar
Ergebnis: FooFoo; BarBar
Das Ergebnis ist genau wie wir erwartet hatten. Wenn Sie sich die Ressourcenmethode ansehen, geben wir nur diese Zeichenfolge zurück foo.foo + "; " + bar.bar
, die aus den beiden JSON-Objekten stammt.
Einige Beispiele für verschiedene JAX-RS-Clients finden Sie unter den folgenden Links. Sie sehen auch einige serverseitige Beispiele aus diesen verschiedenen JAX-RS-Implementierungen. Jeder Link sollte irgendwo einen Link zur offiziellen Dokumentation für diese Implementierung enthalten
Es gibt andere JAX-RS-Implementierungen, aber Sie müssen die Dokumentation dafür selbst finden. Die oben genannten drei sind die einzigen, mit denen ich Erfahrung habe.
Was Javascript-Clients betrifft, sehe ich die meisten Beispiele (z. B. einige davon beinhalten das Setzen Content-Type
von undefined / false (using FormData
), damit der Browser damit umgehen kann. Dies funktioniert jedoch nicht für uns, da der Browser das nicht setzt Content-Type
für jedes Teil. Und der Standardtyp ist text/plain
.
Ich bin mir sicher, dass es Bibliotheken gibt, in denen der Typ für jedes Teil festgelegt werden kann. Um Ihnen jedoch zu zeigen, wie dies manuell durchgeführt werden kann, werde ich ein Beispiel veröffentlichen (von hier aus ein wenig Hilfe erhalten . Ich werde Angular verwenden , aber die grunzende Arbeit beim Aufbau des Entitätskörpers wird einfaches altes Javascript sein.
<!DOCTYPE html>
<html ng-app="multipartApp">
<head>
<script src="js/libs/angular.js/angular.js"></script>
<script>
angular.module("multipartApp", [])
.controller("defaultCtrl", function($scope, $http) {
$scope.sendData = function() {
var foo = JSON.stringify({foo: "FooFoo"});
var bar = JSON.stringify({bar: "BarBar"});
var boundary = Math.random().toString().substr(2);
var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;
$http({
url: "/api/foobar",
headers: { "Content-Type": header },
data: createRequest(foo, bar, boundary),
method: "POST"
}).then(function(response) {
$scope.result = response.data;
});
};
function createRequest(foo, bar, boundary) {
var multipart = "";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=foo"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + foo + "\r\n";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=bar"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + bar + "\r\n";
multipart += "--" + boundary + "--\r\n";
return multipart;
}
});
</script>
</head>
<body>
<div ng-controller="defaultCtrl">
<button ng-click="sendData()">Send</button>
<p>{{result}}</p>
</div>
</body>
</html>
Der interessante Teil ist die createRequest
Funktion. Hier erstellen wir das Multipart, setzen das Content-Type
von jedem Teil auf application/json
und verketten das Stringified foo
und die bar
Objekte zu jedem Teil. Wenn Sie mit dem mehrteiligen Format nicht vertraut sind, finden Sie hier weitere Informationen . Der andere interessante Teil ist der Header. Wir setzen es auf multipart/form-data
.
Unten ist das Ergebnis. In Angular habe ich das Ergebnis nur verwendet, um es im HTML-Code mit anzuzeigen $scope.result = response.data
. Die Schaltfläche, die Sie sehen, diente nur dazu, die Anfrage zu stellen. Sie sehen auch die Anforderungsdaten in Firebug
3. Wickeln Sie sie einfach in ein einzelnes übergeordnetes Objekt
Diese Option sollte selbsterklärend sein, wie andere bereits erwähnt haben.