在 Spring REST 控制器中读取 HTTP 标头

Posted

技术标签:

【中文标题】在 Spring REST 控制器中读取 HTTP 标头【英文标题】:Reading HTTP headers in a Spring REST controller 【发布时间】:2015-03-28 08:32:22 【问题描述】:

我正在尝试在基于 Spring 的 REST API 中读取 HTTP 标头。我关注了this。但是我收到了这个错误:

没有找到类 java.lang.String 的消息正文阅读器, 内容类型:application/octet-stream

我是 Java 和 Spring 的新手,所以无法弄清楚。

这就是我的调用的样子:

@WebService(serviceName = "common")
@Consumes( MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON )
public interface CommonApiService 

    @GET
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/data")
    public ResponseEntity<Data> getData(@RequestHeader(value="User-Agent") String userAgent, @DefaultValue ("") @QueryParam("ID") String id);

我试过@Context:在这种情况下HTTPHeader是null

如何从 HTTP 标头中获取值?

【问题讨论】:

您的实际请求以application/octect-stream 的形式发送,Spring 无法将其反序列化回字符串数据类型。将其设置为application/json(如果您在类路径上有杰克逊)或您期望使用的任何内容,您可能会到达某个地方。您是否有机会尝试将文件上传到您的 REST 控制器? 我正在使用 Google Postman 拨打电话。我将内容类型设置为 application/json 并仍然收到错误。 【参考方案1】:

您得到的错误似乎与 RequestHeader 无关。

而且您似乎将 Spring REST 服务与 JAX-RS 混淆了,您的方法签名应该类似于:

@RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
@ResponseBody
public ResponseEntity<Data> getData(@RequestHeader(value="User-Agent") String userAgent, @RequestParam(value = "ID", defaultValue = "") String id) 
    // your code goes here

你的 REST 类应该有如下注释:

@Controller
@RequestMapping("/rest/")

关于实际问题,另一种获取 HTTP 标头的方法是将 HttpServletRequest 插入到您的方法中,然后从那里获取所需的标头。

例子:

@RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
@ResponseBody
public ResponseEntity<Data> getData(HttpServletRequest request, @RequestParam(value = "ID", defaultValue = "") String id) 
    String userAgent = request.getHeader("user-agent");

不用担心 HttpServletRequest 的注入,因为 Spring 为您提供了魔法;)

【讨论】:

您仍在混合框架,您正在尝试使用 jax-rs 和 Spring rest。看看我的和@JamesMassey 的答案,以便使用 spring rest api。【参考方案2】:

我将举一个例子来说明我是如何为我的控制器读取 REST 标头的。如果我有需要读取的数据,我的控制器只接受 application/json 作为请求类型。我怀疑你的问题是你有一个 Spring 不知道如何处理的 application/octet-stream。

通常我的控制器是这样的:

@Controller
public class FooController 
    @Autowired
    private DataService dataService;

    @RequestMapping(value="/foo/", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Data> getData(@RequestHeader String dataId)
        return ResponseEntity.newInstance(dataService.getData(dataId);
    

现在这里有很多代码在后台做一些事情,所以我会为你分解它。

ResponseEntity 是每个控制器返回的自定义对象。它包含一个允许创建新实例的静态工厂。我的数据服务是一个标准的服务类。

魔术发生在幕后,因为您使用的是 JSON,您需要告诉 Spring 使用 Jackson 来映射 HttpRequest 对象,以便它知道您在处理什么。

您可以通过在配置的 &lt;mvc:annotation-driven&gt; 块中指定它来做到这一点

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

ObjectMapper 只是 com.fasterxml.jackson.databind.ObjectMapper 的扩展,Jackson 使用它来实际将您的请求从 JSON 映射到对象。

我怀疑您遇到异常是因为您没有指定可以将 Octet-Stream 读入对象的映射器,或者 Spring 可以处理的东西。如果您尝试上传文件,那完全是另外一回事。

所以我发送到我的控制器的请求看起来像这样只是有一个名为dataId 的额外标头。

如果您想将其更改为请求参数并使用@RequestParam String dataId 从请求中读取 ID,您的请求将类似于以下内容:

contactId : "fooId" 

此请求参数可以任意复杂。您可以将整个对象序列化为 JSON,将其作为请求参数发送,然后 Spring 会(使用 Jackson)将其序列化回可供您使用的 Java 对象。

控制器中的示例:

@RequestMapping(value = "/penguin Details/", method = RequestMethod.GET)
@ResponseBody
public DataProcessingResponseDTO<Pengin> getPenguinDetailsFromList(
        @RequestParam DataProcessingRequestDTO jsonPenguinRequestDTO)

请求发送:

jsonPengiunRequestDTO: 
    "draw": 1,
    "columns": [
        
            "data": 
                "_": "toAddress",
                "header": "toAddress"
            ,
            "name": "toAddress",
            "searchable": true,
            "orderable": true,
            "search": 
                "value": "",
                "regex": false
            
        ,
        
            "data": 
                "_": "fromAddress",
                "header": "fromAddress"
            ,
            "name": "fromAddress",
            "searchable": true,
            "orderable": true,
            "search": 
                "value": "",
                "regex": false
            
        ,
        
            "data": 
                "_": "customerCampaignId",
                "header": "customerCampaignId"
            ,
            "name": "customerCampaignId",
            "searchable": true,
            "orderable": true,
            "search": 
                "value": "",
                "regex": false
            
        ,
        
            "data": 
                "_": "penguinId",
                "header": "penguinId"
            ,
            "name": "penguinId",
            "searchable": false,
            "orderable": true,
            "search": 
                "value": "",
                "regex": false
            
        ,
        
            "data": 
                "_": "validpenguin",
                "header": "validpenguin"
            ,
            "name": "validpenguin",
            "searchable": true,
            "orderable": true,
            "search": 
                "value": "",
                "regex": false
            
        ,
        
            "data": 
                "_": "",
                "header": ""
            ,
            "name": "",
            "searchable": false,
            "orderable": false,
            "search": 
                "value": "",
                "regex": false
            
        
    ],
    "order": [
        
            "column": 0,
            "dir": "asc"
        
    ],
    "start": 0,
    "length": 10,
    "search": 
        "value": "",
        "regex": false
    ,
    "objectId": "30"

在提供给控制器供我使用之前,它会自动序列化回 DataProcessingRequestDTO 对象。

如您所见,这非常强大,允许您将数据从 JSON 序列化为对象,而无需编写任何代码。您可以对 @RequestParam@RequestBody 执行此操作,这允许您分别访问参数或请求正文中的 JSON。

现在您有了一个具体的示例,一旦您将请求类型更改为 application/json,您应该不会有任何问题。

【讨论】:

【参考方案3】:

不是在每个方法中都使用HttpServletRequest 对象,而是通过构造函数自动连接来保持控制器的上下文。然后就可以从控制器的所有方法中访问了。

public class OAuth2ClientController 
    @Autowired
    private OAuth2ClientService oAuth2ClientService;

    private HttpServletRequest request;

    @Autowired
    public OAuth2ClientController(HttpServletRequest request) 
        this.request = request;
    

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<String> createClient(@RequestBody OAuth2Client client) 
        System.out.println(request.getRequestURI());
        System.out.println(request.getHeader("Content-Type"));

        return ResponseEntity.ok();
    

【讨论】:

在不使用代码生成在 swagger 定义中声明它们的情况下获取标题非常有用。谢谢

以上是关于在 Spring REST 控制器中读取 HTTP 标头的主要内容,如果未能解决你的问题,请参考以下文章

REST API 在 Spring Boot 中删除 HTTP 请求

为啥 Spring Boot 中的 REST 控制器返回 HTTP 状态 404 – 未找到

Spring Boot - 如何在 REST 控制器 HTTP PUT 上允许 CORS

在threadlocal中保持“当前用户”

使用 HTTP 标头的 Spring Security

Spring REST Controller 没有响应 Angular 请求