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

Accepting and configuring JSON in a HTTP-triggered GCP function doesn't work #1083

Open
mijgame opened this issue Oct 21, 2023 · 3 comments
Open

Comments

@mijgame
Copy link

mijgame commented Oct 21, 2023

Describe the bug
I'm trying to develop a HTTP-triggered serverless function that accepts a relatively simple HTTP POST request with a JSON payload. I'm having trouble configuring and working with the JSON mappers. I feel like I'm both running into a bug and missing something; it would be great if you could provide some insights. I'm using version 4.0.5.

Sample
I want to receive this HTTP request:

{
  "signal_id": "1",
  "signal_name": "test-signal",
  "side": "buy",
  "instrument_name": "btcusd",
  "amount": 0.1,
  "type": "limit",
  "exchange": "deribit"
}

And deserialize it into the following class:

data class Signal(
    @JsonProperty("signal_id")
    val signalId: String,

    @JsonProperty("signal_name")
    val signalName: String,

    @JsonProperty("side")
    val side: String,

    @JsonProperty("instrument_name")
    val instrumentName: String,

    @JsonProperty("amount")
    val amount: Double,

    @JsonProperty("type")
    val type: String,

    @JsonProperty("exchange")
    val exchange: String
)

As you can see I'm using JsonProperty here, which is a Jackson property. I've tried to use Gson (which seems to be the default) with SerializedName. This does not work at all for some reason. It simply does not deserialize into these properties, leaving them null if the name in the request doesn't match the property name. Note I'm also providing the application/json content-type header.

Switching from GSON to Jackson does not work
According to the documentation, setting spring.cloud.function.preferred-json-mapper to jackson in the application configuration (application.properties in my case) should work. This does not seem to have any effect however. It is still using GSON. I've verified this by checking the value of System.getProperty(ContextFunctionCatalogAutoConfiguration.JSON_MAPPER_PROPERTY). Other properties configured in the application.properties file are picked up as expected. The only way I can instruct the system to use Jackson is by using System.setProperty(ContextFunctionCatalogAutoConfiguration.JSON_MAPPER_PROPERTY, "jackson") in the init method of the application class.

Handler with Jackson configured doesn't work
I've defined the following handler:

    @Bean
    open fun handleSignal(): Function<Signal, String> {
        return Function { value: Signal ->
            LogManager.getLogger().info(value)
            "succeeded"
        }
    }

With gson as the JSON-mapper, this code runs just fine (although Signal contains nulls as mentioned before). When using jackson, calling the handler fails with the following error:

class org.eclipse.jetty.server.Request$1 cannot be cast to class org.ldq.common.domain.models.Signal (org.eclipse.jetty.server.Request$1 is in unnamed module of loader org.codehaus.plexus.classworlds.realm.ClassRealm @14eb928b; <my namespace>.Signal is in unnamed module of loader com.google.cloud.functions.invoker.runner.Invoker$FunctionClassLoader @386a9877)

Please let me know if you need more information.

@olegz
Copy link
Contributor

olegz commented Oct 23, 2023

Without seeing a full stack trace or example that reproduces the issue there is nothing to go by

@mijgame
Copy link
Author

mijgame commented Oct 24, 2023

Hi,

Please see a reproduction here:
repository

I'm using the following HTTP call (in the Intellij .http file format):

POST http://localhost:8080/handleSignal
Content-Type: application/json

{
  "signal_id": "1",
  "signal_name": "test-signal",
  "side": "buy",
  "instrument_name": "btcusd",
  "amount": 0.1,
  "type": "limit",
  "exchange": "deribit"
}

When you clone the repo, spring.cloud.function.preferred-json-mapper is set to jackson. But when you call the application with this payload, you'll see a gson stacktrace like this:

GSON stacktrace
com.google.gson.JsonIOException: Failed making field 'java.net.Proxy#type' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:38) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:286) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.getAdapter(Gson.java:556) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.getAdapter(Gson.java:556) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:55) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196) ~[gson-2.10.1.jar:na]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.toJson(Gson.java:842) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.toJson(Gson.java:812) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.toJson(Gson.java:759) ~[gson-2.10.1.jar:na]
        at com.google.gson.Gson.toJson(Gson.java:736) ~[gson-2.10.1.jar:na]
        at org.springframework.cloud.function.json.GsonMapper.toJson(GsonMapper.java:65) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.context.config.JsonMessageConverter.convertToInternal(JsonMessageConverter.java:143) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.messaging.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:201) ~[spring-messaging-6.0.12.jar:6.0.12]
        at org.springframework.messaging.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:191) ~[spring-messaging-6.0.12.jar:6.0.12]
        at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.toMessage(SmartCompositeMessageConverter.java:158) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.convertOutputIfNecessary(SimpleFunctionRegistry.java:1237) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:742) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:577) ~[spring-cloud-function-context-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.adapter.gcp.FunctionInvoker.service(FunctionInvoker.java:120) ~[spring-cloud-function-adapter-gcp-4.0.5.jar:4.0.5]
        at org.springframework.cloud.function.adapter.gcp.GcfJarLauncher.service(GcfJarLauncher.java:53) ~[spring-cloud-function-adapter-gcp-4.0.5.jar:4.0.5]
        at com.google.cloud.functions.invoker.HttpFunctionExecutor.service(HttpFunctionExecutor.java:68) ~[na:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) ~[java-function-invoker-1.3.0.jar:na]
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) ~[na:na]
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:554) ~[na:na]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) ~[na:na]
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440) ~[na:na]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) ~[na:na]
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:505) ~[na:na]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) ~[na:na]
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355) ~[na:na]
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[na:na]
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[na:na]
        at com.google.cloud.functions.invoker.runner.Invoker$NotFoundHandler.handle(Invoker.java:474) ~[na:na]
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[na:na]
        at org.eclipse.jetty.server.Server.handle(Server.java:516) ~[na:na]
        at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487) ~[na:na]
        at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732) ~[na:na]
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479) ~[na:na]
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) ~[na:na]
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) ~[na:na]
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) ~[na:na]
        at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) ~[na:na]
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) ~[na:na]
        at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) ~[na:na]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private java.net.Proxy$Type java.net.Proxy.type accessible: module java.base does not "opens java.net" to unnamed module @7f86efbc
        at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391) ~[na:na]
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367) ~[na:na]
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315) ~[na:na]
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183) ~[na:na]
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:177) ~[na:na]
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:35) ~[gson-2.10.1.jar:na]
        ... 64 common frames omitted

@mijgame
Copy link
Author

mijgame commented Nov 1, 2023

Hi @olegz , is this enough for you to go on?

ww2406 pushed a commit to ww2406/cres-offer-api that referenced this issue Feb 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants