[Опыт наступания на ямы] Помните проблему с искажением Webflux

Spring Boot

задний план

  • После обновления Spring Boot 2.2.4 есть изменения при возврате jsoncontent-typeЗависит отapplication/json;charset=UTF-8сталapplication/json;
  • И это приведет к искажению символов в приложениях, кодировка которых по умолчанию не UTF-8.

причина

  • Так почему? Основная причинаJackson2CodecSupportизDEFAULT_MIME_TYPESудаленныйCharsetвариант, поэтому на выходе не будетCharsetохватывать
  • Итак, как Spring определяетContent-Typeчто о? Можно ли его добавить посередине?
  • Анализируя ссылку вызова, можно обнаружить, чтоEncoderHttpMessageWriterсделал updateContentType
  • Так как его обновить?
  • Видно, что он сначала берется из MessageHeader, если не получен, то используется дефолт, если дефолта нет, то используется дефолт.AbstractMessageWriterResultHandlerВыберите лучший MediaType
  • Так что же по умолчанию?
  • Это первый MimeType кодировщика, а кодирование по умолчанию для Json —Jackson2JsonEncoderследовательноJackson2CodecSupportУдаленная кодировка повлияет на реальный вывод запроса.

решение

  • Из вышеизложенного видно, что решение очень простое.Есть два типа.Один из них заключается в добавлении пользовательского кодировщика.Мимовый тип пользовательского кодировщика по умолчанию можно добавить с помощью charset.
  • Однако стоимость этого метода слишком высока, и улучшить тестирование пользовательского кодировщика сложнее.
  • Итак, второе, добавьте его в заголовок ответа.Content-TypeСтаньте первым выбором, это тоже относительно просто, просто добавьте WebFilter
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * @author Lambda.J
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContentTypeFilter implements WebFilter {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
		return chain.filter(exchange);
	}
}
  • После его добавления в нормальном выводе есть Charset, но после запуска теста выясняется, что проблема с ненормальной ссылкой все равно есть, а charset нет.Почему так?
  • Отслеживая исходный код, получается, что RequestMappingHandlerAdapter будет очищать все заголовки, связанные с Content, при обработке исключений.На данный момент цепочка фильтров завершена.Если вы хотите переписать, вы можете переписать только HandlerResultHandler.
  • Переписывать HandlerResultHandler слишком громоздко, а переписывать для каждого вывода дорого.
  • Поэтому я не придумал хорошего способа, поэтому я принял метод Хака и переписал его с помощью размышлений.Jackson2CodecSupportизDEFAULT_MIME_TYPES
// 添加到 SpringBootApplication 启动类中
static {
		// utf8的兜底方案,webfilter 在 resultHandler 之前,出现错误时会删掉 content-type,所以异常链路需要这边兜底
		addCharsetToJsonContentType();
	}
public static void addCharsetToJsonContentType() {
		Field defaultMimeTypeField = null;
		try {
			defaultMimeTypeField = Jackson2CodecSupport.class.getDeclaredField("DEFAULT_MIME_TYPES");
			defaultMimeTypeField.setAccessible(true);
			Field modifiersField = Field.class.getDeclaredField("modifiers");
			modifiersField.setAccessible(true);
			modifiersField.setInt(defaultMimeTypeField, defaultMimeTypeField.getModifiers() & ~Modifier.FINAL);
			defaultMimeTypeField.set(null,
					Collections.unmodifiableList(Arrays.asList(new MimeType("application", "json", Charsets.UTF_8),
							new MimeType("application", "*+json", Charsets.UTF_8))));
			defaultMimeTypeField.setAccessible(false);
			modifiersField.setInt(defaultMimeTypeField, defaultMimeTypeField.getModifiers() | Modifier.FINAL);
			System.out.println("Already Add Charset To Jackson");
		}
		catch (NoSuchFieldException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}

Связанные знания - ссылка на обработку запроса в WebFlux

  • Как видно из документации Spring, ядром под WebFLux являетсяHttpHandlerа такжеWebHandler, сетевой фреймворк (netty, tomcat или другой) получает запрос и передает его HttpHandler, а затем вызывает окончательный WebHandler.
  • Так как же устроен HttpHandler?
  • Видно, что сначала он получает окончательный webHandler и цепочку фильтров для построения декорированного, а затем оборачивает обработчики исключений и, наконец, становится HttpWebHandlerAdapter для прямого подключения к сетевой структуре.
  • Когда сетевая структура ответит на изменение состояния канала, она вызовет метод обработчика HttpWebHandlerAdapter, а затем, в свою очередь, вызовет цепочку ответственности.
  • Как показано на рисунке выше, видно, что когдаRequestMappingHandlerAdapterизhandleException, цепочка фильтров уже была завершена, поэтому наш ContentTyPilter недействителен

использованная литература