Micronaut POST接口无法接收请求体,获取到Optional.empty问题排查
问题描述
使用Micronaut构建POST接口时,通过Postman或curl调用API,服务器端始终获取到请求体为Optional.empty。GET请求正常,能接收所有请求头,仅请求体存在问题。
控制器代码
import io.micronaut.core.annotation.NonNull; import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpRequest; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import jakarta.inject.Inject; import lombok.extern.log4j.Log4j2; import java.util.Objects; @Controller("/abc/v1") @Log4j2 public class AbcController { @Post("/get-new-user-referral-code") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.TEXT_PLAIN) @WithSpan public GetReferralCodeResponse getNewUserReferralCode(HttpRequest<?> request) { String body = request.getBody().toString(); HttpHeaders headers = request.getHeaders(); log.info("Body = {}", body); return apiResponse; } }
build.gradle配置
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id("com.github.johnrengelman.shadow") version "8.1.1" id("io.micronaut.application") version "4.4.0" id("io.micronaut.aot") version "4.4.0" id("groovy") id("jacoco") } version = "0.1" group = "com.abc" repositories { mavenCentral() } dependencies { annotationProcessor("io.micronaut:micronaut-http-validation") annotationProcessor("io.micronaut.serde:micronaut-serde-processor") implementation("io.micronaut.serde:micronaut-serde-jackson") implementation("io.micronaut:micronaut-management") implementation("io.micronaut:micronaut-http-client") implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0' compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' testCompileOnly 'org.projectlombok:lombok:1.18.30' implementation 'software.amazon.awssdk:bom' implementation 'software.amazon.awssdk:appconfigdata' implementation 'software.amazon.awssdk:sts' implementation 'software.amazon.awssdk:netty-nio-client:2.24.3' implementation 'io.netty:netty-codec-http:4.1.108.Final' implementation 'com.github.luben:zstd-jni:1.5.6-6' implementation 'software.amazon.awssdk:apache-client:2.20.69' implementation("software.amazon.awssdk:url-connection-client:2.17+") implementation 'com.amazonaws:aws-lambda-java-core:1.2.3' implementation 'software.amazon.awssdk:lambda:2.17.206' implementation 'software.amazon.awssdk:dynamodb' implementation 'software.amazon.awssdk:dynamodb-enhanced' implementation 'software.amazon.awssdk:sns' implementation 'software.amazon.awssdk:sqs' implementation 'org.apache.logging.log4j:log4j-api:2.20.0' implementation 'org.apache.logging.log4j:log4j-core:2.20.0' implementation 'org.json:json:20231013' implementation 'org.jsonschema2pojo:jsonschema2pojo-core:1.2.1' // opentelemetry implementation("io.opentelemetry:opentelemetry-exporter-otlp") implementation('io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.3.0') implementation("io.opentelemetry:opentelemetry-exporter-logging") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.reactivex.rxjava2:rxjava:2.2.21") testAnnotationProcessor 'io.micronaut:micronaut-inject-java' implementation("io.micronaut.kafka:micronaut-kafka") developmentOnly("io.micronaut.controlpanel:micronaut-control-panel-ui") developmentOnly("io.micronaut.controlpanel:micronaut-control-panel-management") implementation("io.micronaut.objectstorage:micronaut-object-storage-aws") testImplementation 'org.mockito:mockito-core:3.12.4' } application { mainClass = "com.abc.Application" } java { sourceCompatibility = JavaVersion.toVersion("21") targetCompatibility = JavaVersion.toVersion("21") } graalvmNative.toolchainDetection = false micronaut { runtime("netty") testRuntime("junit5") processing { incremental(true) annotations("com.abc.*") } aot { // Please review carefully the optimizations enabled below // Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details optimizeServiceLoading = false convertYamlToJava = false precomputeOperations = true cacheEnvironment = true optimizeClassLoading = true deduceEnvironment = true optimizeNetty = true replaceLogbackXml = true } } tasks.named("dockerfileNative") { jdkVersion = "21.0.4" }
日志信息
"Unrecognized token 'Optional': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled)
Curl请求
curl --location 'http://localhost:8080/abc/v1/get-new-user-referral-code' \ --header 'Content-Length: 1000' \ --header 'Content-Type: text/plain' \ --data 'abc'
问题分析与解决方案
核心问题1:请求体获取方式错误
当前代码调用request.getBody().toString(),但getBody()返回的是Optional<Object>,直接调用toString()会输出Optional.empty或Optional[abc]这类字符串,而非真正的请求体内容。这也是日志中出现Unrecognized token 'Optional'错误的原因——后续代码将该字符串当作JSON解析,自然失败。
正确的获取方式:
// 明确指定类型为String,让Micronaut正确解析文本请求体 Optional<String> bodyOpt = request.getBody(String.class); String body = bodyOpt.orElse(""); // 处理空请求体场景 log.info("Body = {}", body);
核心问题2:请求头Content-Length不匹配
curl请求中设置了Content-Length: 1000,但实际发送的数据仅3个字符(abc),服务器端读取请求体时会因长度不符触发异常,直接放弃读取,导致请求体为空。
移除错误的Content-Length头即可,curl会自动计算并设置正确长度:
curl --location 'http://localhost:8080/abc/v1/get-new-user-referral-code' \ --header 'Content-Type: text/plain' \ --data 'abc'
额外优化:直接通过方法参数接收请求体
Micronaut支持将请求体直接绑定到方法参数,无需手动从HttpRequest中获取,代码更简洁且不易出错:
@Post("/get-new-user-referral-code") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.TEXT_PLAIN) @WithSpan public GetReferralCodeResponse getNewUserReferralCode(@Body String body, HttpHeaders headers) { log.info("Body = {}", body); return apiResponse; }
内容的提问来源于stack exchange,提问作者holeInWater
相关产品推荐
相关产品推荐

