gpt4 book ai didi

java - 为什么我的 Controller 发送内容类型 "application/octet-stream"?

转载 作者:行者123 更新时间:2023-12-01 18:41:33 26 4
gpt4 key购买 nike

我有一个 REST Controller :

@RequestMapping(value = "greeting", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
@Transactional(readOnly = true)
@ResponseBody
public HttpEntity<GreetingResource> greetingResource(@RequestParam(value = "message", required = false, defaultValue = "World") String message) {
GreetingResource greetingResource = new GreetingResource(String.format(TEMPLATE, message));
greetingResource.add(linkTo(methodOn(AdminController.class).greetingResource(message)).withSelfRel());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<GreetingResource>(greetingResource, responseHeaders, HttpStatus.OK);
}

如您所见,我正在努力指定 Controller 返回的内容类型。

它是通过 REST 客户端访问的:

public String getGreetingMessage() {
String message;
try {
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + "mypassword");
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
GreetingResource greetingResource = responseEntity.getBody();
message = greetingResource.getMessage();
} catch (HttpMessageNotReadableException e) {
message = "The GET request FAILED with the message being not readable: " + e.getMessage();
} catch (HttpStatusCodeException e) {
message = "The GET request FAILED with the HttpStatusCode: " + e.getStatusCode() + "|" + e.getStatusText();
} catch (RuntimeException e) {
message = "The GET request FAILED " + ExceptionUtils.getFullStackTrace(e);
}
return message;
}

http header 由实用程序创建:

static public HttpHeaders createAuthenticationHeaders(String usernamePassword) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
byte[] encodedAuthorisation = Base64.encode(usernamePassword.getBytes());
headers.add("Authorization", "Basic " + new String(encodedAuthorisation));
return headers;
}

网络安全配置和代码运行良好。我使用基于 mockMvc 的集成测试来确保这一点成功。

唯一失败的测试是基于 REST 模板的测试:

@Test
public void testGreeting() throws Exception {
mockServer.expect(requestTo("/admin/greeting")).andExpect(method(HttpMethod.GET)).andRespond(withStatus(HttpStatus.OK));
String message = adminRestClient.getGreetingMessage();
mockServer.verify();
assertThat(message, allOf(containsString("Hello"), containsString("World")));
}

Maven 构建控制台输出中给出的异常是:

java.lang.AssertionError: 
Expected: (a string containing "Hello" and a string containing "World")
got: "The GET request FAILED org.springframework.web.client.RestClientException : Could not extract response: no suitable HttpMessageConverter found for response type [class com.thalasoft.learnintouch.rest.resource.GreetingR esource] and content type [application/octet-stream]\n\tat org.springframework.web.client.HttpMessageConverte rExtractor.extractData(HttpMessageConverterExtract or.java:107)

我在 Java 1.6 版本上使用 Spring Framework 3.2.2.RELEASE 版本和 Spring Security 3.1.4.RELEASE 版本。

首先,我有一个简单的 REST 模板:

@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}

我现在添加了它,希望它会有所帮助:

private static final Charset UTF8 = Charset.forName("UTF-8");

@Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);

Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
GreetingResource.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);

messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}

但它没有改变任何东西,异常保持不变。

我的理解是,它不是 REST 模板需要任何特定的 JSON 配置,而是,由于某种原因,我的 Controller 吐出一些 application/octet-stream 内容类型而不是一些 application/json 内容类型。

有什么线索吗?

一些附加信息...

Web 测试配置中的管理休息客户端 bean:

@Configuration
public class WebTestConfiguration {

@Bean
public AdminRestClient adminRestClient() {
return new AdminRestClient();
}

@Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);

Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
Greeting.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);

messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}

}

基本测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration( classes = { ApplicationConfiguration.class, WebSecurityConfig.class, WebConfiguration.class, WebTestConfiguration.class })
@Transactional
public abstract class AbstractControllerTest {

@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
private FilterChainProxy springSecurityFilterChain;

@Autowired
protected RestTemplate restTemplate;

protected MockRestServiceServer mockServer;

@Before
public void setup() {
this.mockServer = MockRestServiceServer.createServer(restTemplate);
}

}

网络初始化类:

public class WebInit implements WebApplicationInitializer {

private static Logger logger = LoggerFactory.getLogger(WebInit.class);

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerListener(servletContext);

registerDispatcherServlet(servletContext);

registerJspServlet(servletContext);

createSecurityFilter(servletContext);
}

private void registerListener(ServletContext servletContext) {
// Create the root application context
AnnotationConfigWebApplicationContext appContext = createContext(ApplicationConfiguration.class, WebSecurityConfig.class);

// Set the application display name
appContext.setDisplayName("LearnInTouch");

// Create the Spring Container shared by all servlets and filters
servletContext.addListener(new ContextLoaderListener(appContext));
}

private void registerDispatcherServlet(ServletContext servletContext) {
AnnotationConfigWebApplicationContext webApplicationContext = createContext(WebConfiguration.class);

ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
dispatcher.setLoadOnStartup(1);

Set<String> mappingConflicts = dispatcher.addMapping("/");

if (!mappingConflicts.isEmpty()) {
for (String mappingConflict : mappingConflicts) {
logger.error("Mapping conflict: " + mappingConflict);
}
throw new IllegalStateException(
"The servlet cannot be mapped to '/'");
}
}

private void registerJspServlet(ServletContext servletContext) {
}

private AnnotationConfigWebApplicationContext createContext(final Class... modules) {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(modules);
return appContext;
}

private void createSecurityFilter(ServletContext servletContext) {
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
}

}

网络配置:

@Configuration
@EnableWebMvc
@EnableEntityLinks
@ComponentScan(basePackages = "com.thalasoft.learnintouch.rest.controller")
public class WebConfiguration extends WebMvcConfigurerAdapter {

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableArgumentResolver resolver = new PageableArgumentResolver();
resolver.setFallbackPageable(new PageRequest(1, 10));
resolvers.add(new ServletWebArgumentResolverAdapter(resolver));
super.addArgumentResolvers(resolvers);
}

}

应用程序配置目前为空:

@Configuration
@Import({ ApplicationContext.class })
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {

// Declare "application" scope beans here, that is, beans that are not only used by the web context

}

最佳答案

我之前也有疑问,但现在你已经发布了所有内容,这就是事实。假设您在 getGreetingMessage() 方法中使用的 RestTemplate 对象与 @Bean 方法中声明的对象相同,问题就从这里开始

this.mockServer = MockRestServiceServer.createServer(restTemplate);

此调用会覆盖 RestTemplate 对象在内部通过模拟使用的默认 ClientHttpRequestFactory 对象。在您的 getGreetingMessage() 方法中,此调用

ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);

实际上并不通过网络。 RestTemplate 使用模拟的 ClientHttpRequestFactory 创建一个假的 ClientHttpRequest,它生成一个假的 ClientHttpResponse,它没有Content-Type header 。当 RestTemplate 查看 ClientHttpResponse 以确定其 Content-Type 但没有找到时,它会假定 application/octet-默认情况下流

因此,您的 Controller 没有设置内容类型,因为您的 Controller 从未被命中。 RestTemplate 正在为您的响应使用默认内容类型,因为它是模拟的并且实际上并不包含该内容类型。

<小时/>

来自您的评论:

I wonder if I understand what the mock server is testing. I understand it is to be used in acceptance testing scenario. Is it supposed to hit the controller at all ?

MockRestServiceServer 的 javadoc状态:

Main entry point for client-side REST testing. Used for tests that involve direct or indirect (through client code) use of the RestTemplate. Provides a way to set up fine-grained expectations on the requests that will be performed through the RestTemplate and a way to define the responses to send back removing the need for an actual running server.

换句话说,就好像您的应用程序服务器不存在一样。因此,您可以抛出您想要的任何期望(和实际返回值)并测试客户端发生的任何情况。因此,您不是在测试服务器,而是在测试客户端。

您确定您不是在寻找 MockMvc ,即

Main entry point for server-side Spring MVC test support.

您可以设置它以在集成环境中实际使用您的@Controller bean。您实际上并未发送 HTTP 请求,但 MockMvc 正在模拟它们的发送方式以及您的服务器将如何响应。

关于java - 为什么我的 Controller 发送内容类型 "application/octet-stream"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19792160/

26 4 0