001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.converter; 018 019import java.io.IOException; 020import java.nio.charset.Charset; 021import java.nio.charset.StandardCharsets; 022import java.util.ArrayList; 023import java.util.List; 024 025import org.springframework.http.HttpHeaders; 026import org.springframework.http.HttpInputMessage; 027import org.springframework.http.HttpOutputMessage; 028import org.springframework.http.MediaType; 029import org.springframework.lang.Nullable; 030import org.springframework.util.Assert; 031import org.springframework.util.StreamUtils; 032 033/** 034 * Implementation of {@link HttpMessageConverter} that can read and write strings. 035 * 036 * <p>By default, this converter supports all media types (<code>*/*</code>), 037 * and writes with a {@code Content-Type} of {@code text/plain}. This can be overridden 038 * by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property. 039 * 040 * @author Arjen Poutsma 041 * @author Juergen Hoeller 042 * @since 3.0 043 */ 044public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> { 045 046 private static final MediaType APPLICATION_PLUS_JSON = new MediaType("application", "*+json"); 047 048 /** 049 * The default charset used by the converter. 050 */ 051 public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; 052 053 054 @Nullable 055 private volatile List<Charset> availableCharsets; 056 057 private boolean writeAcceptCharset = false; 058 059 060 /** 061 * A default constructor that uses {@code "ISO-8859-1"} as the default charset. 062 * @see #StringHttpMessageConverter(Charset) 063 */ 064 public StringHttpMessageConverter() { 065 this(DEFAULT_CHARSET); 066 } 067 068 /** 069 * A constructor accepting a default charset to use if the requested content 070 * type does not specify one. 071 */ 072 public StringHttpMessageConverter(Charset defaultCharset) { 073 super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL); 074 } 075 076 077 /** 078 * Whether the {@code Accept-Charset} header should be written to any outgoing 079 * request sourced from the value of {@link Charset#availableCharsets()}. 080 * The behavior is suppressed if the header has already been set. 081 * <p>As of 5.2, by default is set to {@code false}. 082 */ 083 public void setWriteAcceptCharset(boolean writeAcceptCharset) { 084 this.writeAcceptCharset = writeAcceptCharset; 085 } 086 087 088 @Override 089 public boolean supports(Class<?> clazz) { 090 return String.class == clazz; 091 } 092 093 @Override 094 protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException { 095 Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); 096 return StreamUtils.copyToString(inputMessage.getBody(), charset); 097 } 098 099 @Override 100 protected Long getContentLength(String str, @Nullable MediaType contentType) { 101 Charset charset = getContentTypeCharset(contentType); 102 return (long) str.getBytes(charset).length; 103 } 104 105 106 @Override 107 protected void addDefaultHeaders(HttpHeaders headers, String s, @Nullable MediaType type) throws IOException { 108 if (headers.getContentType() == null ) { 109 if (type != null && type.isConcrete() && 110 (type.isCompatibleWith(MediaType.APPLICATION_JSON) || 111 type.isCompatibleWith(APPLICATION_PLUS_JSON))) { 112 // Prevent charset parameter for JSON.. 113 headers.setContentType(type); 114 } 115 } 116 super.addDefaultHeaders(headers, s, type); 117 } 118 119 @Override 120 protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException { 121 HttpHeaders headers = outputMessage.getHeaders(); 122 if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) { 123 headers.setAcceptCharset(getAcceptedCharsets()); 124 } 125 Charset charset = getContentTypeCharset(headers.getContentType()); 126 StreamUtils.copy(str, charset, outputMessage.getBody()); 127 } 128 129 130 /** 131 * Return the list of supported {@link Charset Charsets}. 132 * <p>By default, returns {@link Charset#availableCharsets()}. 133 * Can be overridden in subclasses. 134 * @return the list of accepted charsets 135 */ 136 protected List<Charset> getAcceptedCharsets() { 137 List<Charset> charsets = this.availableCharsets; 138 if (charsets == null) { 139 charsets = new ArrayList<>(Charset.availableCharsets().values()); 140 this.availableCharsets = charsets; 141 } 142 return charsets; 143 } 144 145 private Charset getContentTypeCharset(@Nullable MediaType contentType) { 146 if (contentType != null && contentType.getCharset() != null) { 147 return contentType.getCharset(); 148 } 149 else if (contentType != null && 150 (contentType.isCompatibleWith(MediaType.APPLICATION_JSON) || 151 contentType.isCompatibleWith(APPLICATION_PLUS_JSON))) { 152 // Matching to AbstractJackson2HttpMessageConverter#DEFAULT_CHARSET 153 return StandardCharsets.UTF_8; 154 } 155 else { 156 Charset charset = getDefaultCharset(); 157 Assert.state(charset != null, "No default charset"); 158 return charset; 159 } 160 } 161 162}