Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to customize protobuff media type header #613

Merged
merged 2 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Auto detect text files and perform LF normalization
* text=lf
* text=auto

*.java text eol=lf
*.groovy text eol=lf
*.html text eol=lf
*.kt text eol=lf
*.kts text eol=lf
*.md text diff=markdown eol=lf
*.java text
*.html text
*.kt text
*.kts text
*.md text diff=markdown
*.py text diff=python executable
*.pl text diff=perl executable
*.pm text diff=perl
*.css text diff=css eol=lf
*.js text eol=lf
*.sql text eol=lf
*.q text eol=lf
*.css text diff=css
*.js text
*.sql text
*.q text

*.sh text eol=lf
gradlew text eol=lf
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/central-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: '11'
- name: Publish to Sonatype OSSRH
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: Optional setup step
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: '11'
- name: Publish to Sonatype Snapshots
if: success()
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: '11'
- name: Set the current release version
id: release_version
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: 11
- name: Optional setup step
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,30 @@
*/
package io.micronaut.protobuf.codec;

import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import io.micronaut.core.io.buffer.ByteBuffer;
import io.micronaut.core.io.buffer.ByteBufferFactory;
import io.micronaut.core.type.Argument;
import io.micronaut.http.MediaType;
import io.micronaut.http.codec.CodecException;
import io.micronaut.http.codec.MediaTypeCodec;

import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import io.micronaut.core.io.buffer.ByteBuffer;
import io.micronaut.core.io.buffer.ByteBufferFactory;
import io.micronaut.core.type.Argument;
import io.micronaut.http.MediaType;
import io.micronaut.http.codec.CodecException;
import io.micronaut.http.codec.MediaTypeCodec;

import jakarta.inject.Singleton;

import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;

/**
* Protocol buffers codec.
*
Expand All @@ -43,26 +47,41 @@
*/
@Singleton
public class ProtobufferCodec implements MediaTypeCodec {

/**
* This Header is to say the fully qualified name of the message builder to use.
* This is needed when the request is untyped
*/
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message";
/**
* Protobuffer encoded data: application/x-protobuf.
*/
public static final String PROTOBUFFER_ENCODED = "application/x-protobuf";
/**
* This Header is to say the fully qualified name of the message builder to use.
* This is needed when the request is untyped
* Protobuffer encoded data: application/protobuf.
*/
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message";
public static final String PROTOBUFFER_ENCODED2 = "application/protobuf";
/**
* Protobuffer encoded data: application/x-protobuf.
*/
public static final MediaType PROTOBUFFER_ENCODED_TYPE = new MediaType(PROTOBUFFER_ENCODED);
/**
* Protobuffer encoded data: application/protobuf.
*/
public static final MediaType PROTOBUFFER_ENCODED_TYPE2 = new MediaType(PROTOBUFFER_ENCODED2);
/**
* List of default protobuf media types.
*/
public static final List<MediaType> DEFAULT_MEDIA_TYPES = Arrays.asList(PROTOBUFFER_ENCODED_TYPE, PROTOBUFFER_ENCODED_TYPE2);

private final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<>();

private final ExtensionRegistry extensionRegistry;
private List<MediaType> mediaTypes = DEFAULT_MEDIA_TYPES;

/**
* Default constructor.
*
* @param extensionRegistry The extension registry
*/
public ProtobufferCodec(ExtensionRegistry extensionRegistry) {
Expand All @@ -76,16 +95,23 @@ public boolean supportsType(Class<?> type) {

@Override
public Collection<MediaType> getMediaTypes() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(PROTOBUFFER_ENCODED_TYPE);
return mediaTypes;
}

/**
* Method to customize media types for this codec.
*
* @param mediaTypes media types for which need use this codec.
*/
public void setMediaTypes(List<MediaType> mediaTypes) {
this.mediaTypes = Collections.unmodifiableList(new ArrayList<>(mediaTypes));
}

@Override
public <T> T decode(Argument<T> type, InputStream inputStream) throws CodecException {
try {
Message.Builder builder = getBuilder(type)
.orElseThrow(() -> new CodecException("Unable to create builder"));
.orElseThrow(() -> new CodecException("Unable to create builder"));
if (type.hasTypeVariables()) {
throw new IllegalStateException("Generic type arguments are not supported");
} else {
Expand All @@ -104,7 +130,7 @@ public <T> T decode(Argument<T> type, ByteBuffer<?> buffer) throws CodecExceptio
return (T) buffer.toByteArray();
} else {
Message.Builder builder = getBuilder(type)
.orElseThrow(() -> new CodecException("Unable to create builder"));
.orElseThrow(() -> new CodecException("Unable to create builder"));
if (type.hasTypeVariables()) {
throw new IllegalStateException("Generic type arguments are not supported");
} else {
Expand All @@ -124,7 +150,7 @@ public <T> T decode(Argument<T> type, byte[] bytes) throws CodecException {
return (T) bytes;
} else {
Message.Builder builder = getBuilder(type)
.orElseThrow(() -> new CodecException("Unable to create builder"));
.orElseThrow(() -> new CodecException("Unable to create builder"));
if (type.hasTypeVariables()) {
throw new IllegalStateException("Generic type arguments are not supported");
} else {
Expand Down Expand Up @@ -181,6 +207,7 @@ public ExtensionRegistry getExtensionRegistry() {
* <p>This method uses a ConcurrentHashMap for caching method lookups.
*
* @param clazz The class.
*
* @return The message builder
*/
public Optional<Message.Builder> getMessageBuilder(Class<? extends Message> clazz) {
Expand All @@ -192,7 +219,7 @@ public Optional<Message.Builder> getMessageBuilder(Class<? extends Message> claz
}

private Optional<Message.Builder> createBuilder(Class<? extends Message> clazz) throws Exception {
return Optional.of ((Message.Builder) getMethod(clazz).invoke(clazz));
return Optional.of((Message.Builder) getMethod(clazz).invoke(clazz));
}

private Method getMethod(Class<? extends Message> clazz) throws NoSuchMethodException {
Expand Down
35 changes: 26 additions & 9 deletions protobuff-support/src/test/groovy/io/micronaut/BaseSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
*/
package io.micronaut

import com.google.protobuf.ExtensionRegistry
import com.google.protobuf.Message
import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Replaces
import io.micronaut.http.HttpHeaders
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.protobuf.codec.ProtobufferCodec
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.websocket.WebSocketClient
import jakarta.inject.Singleton
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
Expand All @@ -46,32 +50,45 @@ abstract class BaseSpec extends Specification {
embeddedServer.getURL()
)

byte[] getMessage(String url, Class aClass) {
byte[] getMessage(String url, Class aClass, String mediaType) {
return httpClient.toBlocking().retrieve(
HttpRequest.GET(url)
.header(ProtobufferCodec.X_PROTOBUF_MESSAGE_HEADER, aClass.name)
.header(HttpHeaders.ACCEPT, ProtobufferCodec.PROTOBUFFER_ENCODED),
.header(ProtobufferCodec.X_PROTOBUF_MESSAGE_HEADER, aClass.name)
.header(HttpHeaders.ACCEPT, mediaType),
byte[].class
)
}

byte[] postMessage(String url, Message message) {
byte[] postMessage(String url, Message message, String mediaType) {
return httpClient.toBlocking().retrieve(
HttpRequest.POST(url, message)
.header(HttpHeaders.CONTENT_TYPE, ProtobufferCodec.PROTOBUFFER_ENCODED)
.header(HttpHeaders.CONTENT_TYPE, mediaType)
.header(ProtobufferCodec.X_PROTOBUF_MESSAGE_HEADER, message.class.name)
.header(HttpHeaders.ACCEPT, ProtobufferCodec.PROTOBUFFER_ENCODED),
.header(HttpHeaders.ACCEPT, mediaType),
byte[].class
)
}

byte[] postMessage(String url, byte[] message) {
byte[] postMessage(String url, byte[] message, String mediaType) {
return httpClient.toBlocking().retrieve(
HttpRequest.POST(url, message)
.header(HttpHeaders.CONTENT_TYPE, ProtobufferCodec.PROTOBUFFER_ENCODED)
.header(HttpHeaders.CONTENT_TYPE, mediaType)
.header(ProtobufferCodec.X_PROTOBUF_MESSAGE_HEADER, message.class.name)
.header(HttpHeaders.ACCEPT, ProtobufferCodec.PROTOBUFFER_ENCODED),
.header(HttpHeaders.ACCEPT, mediaType),
byte[].class
)
}

@Factory
static class SetCutomHeadersConfig {

@Singleton
@Replaces(ProtobufferCodec.class)
ProtobufferCodec init(ExtensionRegistry registry) {
def codec = new ProtobufferCodec(registry)
codec.setMediaTypes([ProtobufferCodec.PROTOBUFFER_ENCODED_TYPE, ProtobufferCodec.PROTOBUFFER_ENCODED_TYPE2, SampleController.MY_PROTO_ENCODED_TYPE])
return codec;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ProgramaticController {
.setLng(-6.266155D)
.build()

@Get(processes = ProtobufferCodec.PROTOBUFFER_ENCODED)
@Get(processes = [ProtobufferCodec.PROTOBUFFER_ENCODED, ProtobufferCodec.PROTOBUFFER_ENCODED2, "my/myAppType"])
Example.GeoPoint city() {
DUBLIN
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,36 @@
package io.micronaut

import com.example.wire.Example
import io.micronaut.protobuf.codec.ProtobufferCodec

class ProgramaticControllerSpec extends BaseSpec {

String url = embeddedServer.getURL().toString() + '/town'

void "sample city should be dublin/using programmatic controller controller"() {
when:'The message is requested from the sever=[#url]'
def response = getMessage(url, Example.GeoPoint.class)
and:'The message is parser'
Example.GeoPoint city = Example.GeoPoint.parseFrom(response)
then:'Should be Dublin'
SampleController.DUBLIN == city
when: 'The message is requested from the sever=[#url]'
def response = getMessage(url, Example.GeoPoint.class, ProtobufferCodec.PROTOBUFFER_ENCODED)
and: 'The message is parser'
Example.GeoPoint city = Example.GeoPoint.parseFrom(response)
then: 'Should be Dublin'
SampleController.DUBLIN == city
}

void "sample city should be dublin/using programmatic controller controller with second codec header"() {
when: 'The message is requested from the sever=[#url]'
def response = getMessage(url, Example.GeoPoint.class, ProtobufferCodec.PROTOBUFFER_ENCODED2)
and: 'The message is parser'
Example.GeoPoint city = Example.GeoPoint.parseFrom(response)
then: 'Should be Dublin'
SampleController.DUBLIN == city
}

void "sample city should be dublin/using programmatic controller controller with custom codec header"() {
when: 'The message is requested from the sever=[#url]'
def response = getMessage(url, Example.GeoPoint.class, SampleController.MY_PROTO_ENCODED)
and: 'The message is parser'
Example.GeoPoint city = Example.GeoPoint.parseFrom(response)
then: 'Should be Dublin'
SampleController.DUBLIN == city
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package io.micronaut

import com.example.wire.Example
import groovy.transform.CompileStatic
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
Expand All @@ -26,17 +27,21 @@ import io.micronaut.protobuf.codec.ProtobufferCodec
@Controller
@CompileStatic
class SampleController {

public static final String MY_PROTO_ENCODED = "my/myAppType"
public static final MediaType MY_PROTO_ENCODED_TYPE = new MediaType(MY_PROTO_ENCODED)

public static Example.GeoPoint DUBLIN = Example.GeoPoint.newBuilder()
.setLat(53.350140D)
.setLng(-6.266155D)
.build()

@Get(value = "/city", processes = ProtobufferCodec.PROTOBUFFER_ENCODED)
@Get(value = "/city", processes = [ProtobufferCodec.PROTOBUFFER_ENCODED, ProtobufferCodec.PROTOBUFFER_ENCODED2, "my/myAppType"])
Example.GeoPoint city() {
DUBLIN
}

@Post(value = "/nearby", processes = ProtobufferCodec.PROTOBUFFER_ENCODED)
@Post(value = "/nearby", processes = [ProtobufferCodec.PROTOBUFFER_ENCODED, ProtobufferCodec.PROTOBUFFER_ENCODED2, "my/myAppType"])
Example.GeoPoint suggestVisitNearBy(@Body Example.GeoPoint point) {
DUBLIN
}
Expand Down
Loading