Protobuf数据传输至JAXRS端点遇媒体类型及内部错误求助
Let's break down your issues one by one and fix them step by step, with clear explanations and corrected code snippets.
1. Initial 415 Unsupported Media Type Error: Root Causes & Fixes
Your first attempt had two critical mistakes that triggered the 415 error:
Mistake 1: Sending Protobuf Schema Instead of Serialized Binary Data
You were sending the text content of a .proto file (the human-readable schema) instead of a serialized Protobuf message instance. The application/x-protobuf media type is designed for binary Protobuf data, not the schema itself.
Mistake 2: Mismatched Content-Type Header
Using Entity.text(inputproto) sets the request's Content-Type to text/plain, but your server endpoint expects application/x-protobuf. This mismatch between what you're sending and what the server accepts causes the 415 error.
Quick Fix for the Initial Approach (Without Custom Providers)
If you want to send raw Protobuf binary data directly:
Client Code:
// First, create and serialize a Protobuf message using your generated Album class Album album = Album.newBuilder() .setSongTitle("Hello World") .setTitle("My Debut Album") .addArtist("Jane Doe") .addReleaseYear(2024) .build(); Client genClient = ClientBuilder.newClient(); WebTarget target = genClient.target("http://localhost:8080/ClientJAXRS/rest/Hello/proto"); // Send binary data with the correct Content-Type header Response res = target.request() .header("Content-Type", "application/x-protobuf") .put(Entity.entity(album.toByteArray(), "application/x-protobuf"));
Server Endpoint (Raw Binary Handling):
@PUT @Path("/proto") @Consumes("application/x-protobuf") @Produces(MediaType.TEXT_PLAIN) public Response getInfo(InputStream protoStream) throws IOException { // Parse the binary stream into an Album instance Album album = Album.parseFrom(protoStream); return Response.ok("Received album: " + album.getSongTitle()).build(); }
2. 500 Internal Server Error: Root Causes & Fixes
Your second attempt with custom MessageBodyWriter/MessageBodyReader was on the right track, but had several key issues:
Mistake 1: Using GET with @Consumes
GET requests do not support request bodies. You need to use POST or PUT to send data to the server.
Mistake 2: Incorrect Getters for Repeated Fields
Your Album message has repeated fields (artist, release_year), so the generated Java methods are getArtistList() and getReleaseYearList() (not getArtist() or getReleaseYear()). Using the wrong getter throws a runtime error, causing the 500.
Mistake 3: Broken MessageBodyWriter
You wrapped the OutputStream with a PrintWriter (for text), but Protobuf's writeTo() writes binary data directly. This corrupts the binary stream and breaks parsing.
Mistake 4: Client Not Sending a Request Body
Your client was making a GET request without sending any Protobuf data, so the server tried to parse an empty body into an Album, leading to an error.
Mistake 5: Missing Provider Registration
You need to register your ProtoMessageBodyWriter with both the JAX-RS server and client so they know how to handle the Album type.
Corrected Implementation
Step 1: Fix the MessageBodyWriter/Reader
@Provider @Produces("application/x-protobuf") @Consumes("application/x-protobuf") public class ProtoMessageBodyHandler implements MessageBodyWriter<Album>, MessageBodyReader<Album> { @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Album.class.isAssignableFrom(type); } @Override public void writeTo(Album album, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException { // Write binary Protobuf data directly to the stream (no PrintWriter!) album.writeTo(out); } @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Album.class.isAssignableFrom(type); } @Override public Album readFrom(Class<Album> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream in) throws IOException { return Album.parseFrom(in); } }
Step 2: Fix the Server Endpoint
Switch to POST and use correct getters for repeated fields:
@POST @Path("/proto-data") @Consumes("application/x-protobuf") @Produces(MediaType.TEXT_PLAIN) public Response getInfo(Album inpAlbum) { StringBuilder sbuilder = new StringBuilder("Input Album:\n"); sbuilder.append("Title: ").append(inpAlbum.getTitle()).append("\n"); sbuilder.append("Artists: ").append(inpAlbum.getArtistList()).append("\n"); sbuilder.append("Release Years: ").append(inpAlbum.getReleaseYearList()).append("\n"); sbuilder.append("Song Title: ").append(inpAlbum.getSongTitle()).append("\n"); return Response.ok(sbuilder.toString()).build(); }
Step 3: Register the Provider
- Server Side: Add the provider to your JAX-RS application (if using an
Applicationsubclass):@ApplicationPath("/rest") public class MyApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); classes.add(HelloResource.class); // Your endpoint class classes.add(ProtoMessageBodyHandler.class); // Register the provider return classes; } } - Client Side: Register the provider when creating the client:
Client client = ClientBuilder.newClient() .register(ProtoMessageBodyHandler.class);
Step 4: Corrected Client Code
Send a POST request with the serialized Album instance:
// Create the Album message Album album = Album.newBuilder() .setSongTitle("Let It Be") .setTitle("Abbey Road") .addArtist("The Beatles") .addReleaseYear(1969) .build(); // Register the provider and send the request Client client = ClientBuilder.newClient() .register(ProtoMessageBodyHandler.class); WebTarget target = client.target("http://localhost:8081/ClientJAXRS/rest/Hello/proto-data"); Response response = target.request() .post(Entity.entity(album, "application/x-protobuf")); // Print the response details System.out.println("Status Code: " + response.getStatus()); System.out.println("Response Body:\n" + response.readEntity(String.class)); // Clean up resources response.close(); client.close();
Key Takeaways
- Protobuf is Binary: Always send serialized Protobuf message instances (binary data), not the schema text.
- Correct Media Type: Use
application/x-protobuffor both request and response content types. - GET vs POST/PUT: Use POST or PUT when sending data in the request body (GET has no body).
- Register Providers: Ensure your custom
MessageBodyWriter/MessageBodyReaderis registered with both server and client. - Use Generated Getters: For repeated fields, use the
List-returning getters (e.g.,getArtistList()).
内容的提问来源于stack exchange,提问作者aΨVaN

