Автоматическое преобразование сетевого протокола связи с экономичного на http

задняя часть Spring JSON HTTP

задний план

При обычном развитии бизнеса встречаются два сценария:

1. Так как rpc framework используется в деле бережливо, то и код пишется бережливо.Однажды мне неожиданно пришел запрос, что фронтенду нужно использовать интерфейс доступа по http, поэтому на использование фреймворка ушло несколько дней Контроллер для всех экономичных интерфейсов.Один уровень инкапсуляции. Поскольку это кросс-язык, и другая сторона не использует бережливость, вам необходимо предоставить интерфейс Http.

2. После написания thrift, для самопроверки нужно написать TestController для проверки правильности кода и запущен ли весь процесс, что очень хлопотно.

Мы сталкивались со многими из этих двух сценариев, поэтому было бы здорово, если бы интерфейс бережливости можно было напрямую преобразовать в интерфейс http сразу после написания интерфейса бережливости.

Глядя на весь Интернет, в условиях быстрой итерации Интернета все больше и больше компаний выбирают быстрые среды сценариев, такие как nodejs, django и rails, для разработки веб-приложений.Для нас серверным языком, выбранным компанией, является Java. Это приводит к большому количеству требований к межъязыковым вызовам. На самом деле Thrift поддерживает множество языков, но каждый раз, когда вы разрабатываете для других языков, вам нужно разрабатывать соответствующие клиенты, а существует множество rpc-фреймворков, которые не поддерживают столько языков, сколько Thrift, поэтому сейчас запускаются микросервисы. (www.servicemesh.cn/), но это все еще очень новинка, вы действительно можете попробовать, если вам нужно попробовать. http и json, естественно, подходят в качестве межъязыковых стандартов, а различные языки имеют зрелые библиотеки классов, поэтому то, как преобразовать tcp rpc framework, например Thrift, в http, более важно для многоязычной поддержки.

RESTful or JSONRPC

RESTful

Сначала я думал о том, как сопоставить интерфейс thrift с RESTful, потому что это больше соответствует интернет-стандарту http, но между TCP rpc и RESTful есть принципиальная разница.Ядро RESTful — это ресурсы, и используются различные методы. в протоколе Http GET, POST, OPTION и т. д. для работы с ресурсами. Если вы хотите сопоставить каждый интерфейс бережливости один за другим, это немного сложно. В конце концов, эти два не могут производить никакой ассоциации. В настоящее время, каждый интерфейс должен быть настроен и сопоставлен, что не дорого.Поскольку я переписал набор контроллеров, решение RESTful было в основном отклонено.

JSONRPC

JSON-RPC — это облегченный протокол удаленного вызова процедур (RPC) без сохранения состояния. Он позволяет работать в одном и том же процессе на основе сокетов, http и многих других сред обмена сообщениями.

JSONRPC по своей сути также является RPC, и его позиционирование похоже на thrfit, и он не требует слишком большого сопоставления протоколов. Поэтому мы решили использовать JSONRPC для преобразования Http.

Объект запроса JSONRPC

Отправка объекта запроса на сервер представляет собой вызов rpc. Объект запроса содержит следующие элементы:

jsonrpc

Строка, указывающая версию протокола JSON-RPC, которая должна быть записана именно как «2.0».

method

Строка, содержащая имя вызываемого метода, имя метода, начинающееся с rpc, а также имя и расширение метода, зарезервированные для rpc внутри соединения с английской точкой (U+002E или ASCII 46), и не может использоваться в других местах. .

params

Значение структурного параметра, необходимое для вызова метода, этот параметр-член можно опустить.

id

Уникальный идентификатор установленного клиента. Значение должно содержать строку, числовое значение или значение NULL. Если член не включен, это считается уведомлением. Значение обычно не равно NULL[1], если это число, оно не должно содержать десятичных знаков[2].

Сервер ДОЛЖЕН ответить тем же значением, если оно включено в объект ответа. Этот элемент используется для связывания контекста между двумя объектами.

[1] Не рекомендуется использовать NULL в качестве значения идентификатора в объекте запроса, поскольку спецификация рассматривает запрос с неизвестным идентификатором как использование значения NULL. Кроме того, поскольку в уведомлениях JSON-RPC 1.0 используются нулевые значения, это может привести к путанице при обработке.

[2] Использование десятичных дробей не является детерминированным, потому что многие десятичные дроби не могут быть точно представлены как двоичные дроби.

уведомлять

Объект запроса, который не содержит элемент "id", является уведомлением. Объект запроса, используемый в качестве уведомления, указывает, что клиент не заинтересован в соответствующем объекте ответа, и нет никакого объекта ответа, который должен быть возвращен клиенту. Сервер НЕ ДОЛЖЕН отвечать на уведомления, в том числе на массовые запросы.

Поскольку у уведомления нет объекта ответа для возврата, уведомление неопределенно определено. Кроме того, клиент не будет знать о каких-либо ошибках (например, о значениях параметров по умолчанию, внутренних ошибках).

структура параметров

Если в вызове rpc есть параметры, это должно быть значение параметра примитивного или структурированного типа, либо индексированный массив, либо объект ассоциативного массива.

  • Индекс: параметр должен быть массивом и содержать значения параметров в порядке, ожидаемом сервером.

  • Имя ассоциации: параметр должен быть объектом и содержать имя члена параметра, соответствующее серверу. Неожиданное имя элемента может вызвать ошибку. Имена должны точно совпадать, включая ожидаемые имена параметров метода и регистр.

объект ответа

При выполнении вызова rpc сервер должен ответить ответом в дополнение к уведомлению. Ответ представлен в виде объекта JSON со следующими членами:

jsonrpc

Строка, указывающая версию протокола JSON-RPC, которая должна быть записана именно как «2.0».

result

Этот член должен быть включен в случае успеха.

Этот член не должен быть включен, если вызов метода вызывает ошибку.

Вызываемый метод на сервере определяет значение этого члена.

error

Этот член должен быть включен в случае сбоя.

Этот член не должен быть включен, если не возникает никаких ошибок.

Значение параметра члена должно быть объектом, определенным в 5.1.

id

Этот элемент должен содержать.

Значение этого члена должно совпадать со значением члена id в объекте запроса.

Если при проверке идентификатора объекта запроса возникает ошибка (например, неправильный параметр или недопустимый запрос), значение должно быть нулевым.

Объект ответа должен содержать либо элемент результата, либо элемент ошибки, но не то и другое одновременно.

объект ошибки

Когда вызов rpc обнаруживает ошибку, возвращаемый объект ответа должен содержать параметр члена ошибки и быть объектом со следующими параметрами члена:

code

Используйте числовое значение, чтобы указать тип ошибки исключения. Должно быть целым числом.

message

Простая строка описания ошибки. Описание должно быть ограничено как можно более коротким предложением.

data

Примитивный или структурированный тип, содержащий дополнительную информацию об ошибке. Этот член может быть проигнорирован. Значение этого члена определяется сервером (например, подробная информация об ошибках, вложенные ошибки и т. д.).

JsonRpc4j

jsonRpc4j — это фреймворк JSONRPC, реализованный на языке Java и использующий JackSon для парсинга JSON. Его адрес на гитхабе:GitHub.com/Брайан снова придет…

В jsonRpc4j он может обрабатывать HTTP-сервер (HttpServletRequest \ HttpServletResponse), поэтому он может помочь нам очень быстро построить https-сервер, используя JsonRpc4j очень просто:

ObjectMapper mapper = new ObjectMapper();

JsonRpcServer skeleton = new JsonRpcServer(mapper, new DemoService(), (Class<?>) service.getClass());

skeleton.handle(req, resp);

Сначала создайте ObjectMapper для преобразования JSON, затем поместите службу, которую нужно превратить в сервер, в JsonRpcServer и, наконец, выполните запрос.

бережливость к http

Для экономии на http используйте Serlvet плюс jsonRpc4j для завершения сопоставления отношений, как показано на следующем рисунке:


HTTP URL

Ключевым моментом в http является то, как сформулировать URL-адрес http.URL-адрес здесь простой, быстрый и понятный, с использованием следующих правил:

POST: servlet-url-pattern + thriftServiceInfaceName

Прежде всего, публичный путь всех методов бережливости сформулирован в сервлете, и все URL-адреса /thrift/* проходят через ThriftSerlvet.

<servlet>

 <servlet-name>thriftSerlvet</servlet-name>

 <servlet-class>com.thrift.ThriftSerlvet</servlet-class>

 </servlet>

 <servlet-mapping>

 <servlet-name>thriftSerlvet</servlet-name>

 <url-pattern>/thrift/*</url-pattern>

 </servlet-mapping>

У нас есть бережливость следующим образом

public class CustomerThriftServiceImpl implements customerService.Iface{

 @Override

 public QueryCustomerResp queryCustomer(QueryRuleReq queryReq) throws TException {

 QueryCustomerResp result = new QueryCustomerResp();

 try {

 result.setCustomr(new Customer());

 result.setStatus(ThriftRespStatusHelper.OK);

 } catch (Exception e) {

 LOGGER.error("查询出现错误{}", e);

 result.setStatus(ThriftRespStatusHelper.failure("查询失败"));

 }

 return result;

 }

}

Итак, наш URL-адрес похож на /thrift/customerService.

ThrifSerlvet

Все наши бережливые запросы будут проходить через этот серввет, а далее код раздачи маршрутизации jsonRpcServer через него выглядит следующим образом:

public class ThriftSerlvet extends HttpServlet {

    public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
    public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";
    public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";

    private final Map<String, JsonRpcServer> rpcServerMap = new ConcurrentHashMap<>();

    private Logger LOGGER = LoggerFactory.getLogger(ThriftSerlvet.class);

    public static final String JSON_FILTER_ID = "thriftPropFilter";

    @Override
    public void init() throws ServletException {
        super.init();
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        Map<String, ThriftServerPublisher> publisherMap = rootContext.getBeansOfType(ThriftServerPublisher.class);
        if (publisherMap == null || publisherMap.size() == 0) {
            return;
        }
        for (ThriftServerPublisher serverPublisher : publisherMap.values()) {
            try {
                Field serviceImplField = serverPublisher.getClass().getDeclaredField("serviceImpl");
                serviceImplField.setAccessible(true);
                Object serveiceImpl = serviceImplField.get(serverPublisher);
                addJsonRpcServer(serveiceImpl, serverPublisher.getServiceSimpleName());
            } catch (Exception e) {
                LOGGER.error("this serverPublisher:{}, get the filed:{} has error", serverPublisher, "serviceImpl", e);
            }
        }
    }

    private void addJsonRpcServer(Object serveiceImpl, String serviceSimpleName) {
        serviceSimpleName = serviceSimpleName.replaceFirst(String.valueOf(serviceSimpleName.charAt(0)), String.valueOf(serviceSimpleName.charAt(0)).toLowerCase());
        LOGGER.info("serverPubliser");
        ObjectMapper mapper = new ObjectMapper();
        SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
        simpleFilterProvider.addFilter(JSON_FILTER_ID, new ThriftPropertiesFilter());
        mapper.setFilterProvider(simpleFilterProvider);
        mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
            @Override
            public Object findFilterId(Annotated a) {
                return JSON_FILTER_ID;
            }
        });
        JsonRpcServer rpcServer = new JsonRpcServer(mapper, serveiceImpl, serveiceImpl.getClass().getSuperclass());
        rpcServer.setInterceptorList(Arrays.asList(new ThriftJsonInterceptor()));
        rpcServerMap.put(serviceSimpleName, rpcServer);

    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
        resp.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, "POST");
        resp.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, "*");
        if (req.getMethod().equalsIgnoreCase("OPTIONS")) {
            resp.sendError(200);
        } else if (req.getMethod().equalsIgnoreCase("POST")) {
            String uri = req.getRequestURI();
            String path = req.getServletPath();
            String serviceName = uri.substring(path.length(), uri.length()).replaceAll("/", "");
            JsonRpcServer rpcServer = rpcServerMap.get(serviceName);
            if (rpcServer == null) {
                resp.sendError(404);
                return;
            }
            rpcServer.handle(req, resp);
        } else {
            //方法不被允许
            resp.sendError(405);
        }

    }

}

Процесс выполнения следующий:

1.init: во время инициализации нам нужно взять все bean-компоненты нашего thriftService из контейнера Spring, а затем создать отдельный JsonRpcServer для каждого сервиса, поместить его в карту и дождаться маршрутизации метода сервиса.

Здесь есть несколько замечаний по поводу инициализации:

  • В ObjectMapper мы фильтруем выходные данные, начинающиеся с set, потому что, когда jackSon преобразует thrift, он будет преобразовывать файлы, сгенерированные самим thrift.

  • Здесь нам нужно настроить в конфигурационном файле spring

<aop:aspectj-autoproxy proxy-target-class="true"/>

Если вы не укажете это, по умолчанию используется прокси-сервер Jdk. Если используется прокси-сервер jdk, вы не сможете получить исходный класс по умолчанию. Здесь необходимо использовать прокси-сервер cglib, чтобы вы могли получить свой собственный исходный Класс через getSuperClass.

2. Сервис относительно простой, мы сначала добавили разрешение на междоменное, а потом указали, что доступ возможен только методом POST.

Модификация JsonRpc4j

Для этого проекта с открытым исходным кодом он не использовался напрямую, а модифицировался, зачем нам его модифицировать?

Давайте просто посмотрим на следующий метод

public Person sayHello(Person person, Type type);

Если мы хотим вызвать эту службу, необходимо передать json:

{"jsonrpc": "2.0", "method": "sayHello", "params": \[{"age":"12","name":"lizhao"},{"type":1}\], "id": 1}

Приведенные выше параметры json и params передаются в массиве, на самом деле мы предпочитаем передавать следующие, потому что этот тип параметра нужно указывать с именем, чтобы он был более читабельным и уменьшал вероятность ошибок:

{"jsonrpc": "2.0", "method": "sayHello", "params": {"person":{"age":"12","name":"lizhao"},"type":{"type":1}}, "id": 1}

Однако, если он будет передан таким образом, он сообщит, что метод не может быть найден.Официальный метод jsonrpc4j заключается в использовании аннотаций для изменения метода на:

public Person sayHello(@JsonRpcParam("person")Person person, @JsonRpcParam("type")Type type);

Но студенты, которые использовали бережливость, знают, что многие коды бережливости генерируются в соответствии с IDL, что вызовет проблему, и аннотации нельзя использовать, потому что после использования аннотаций следующее поколение будет напрямую перезаписано. Итак, здесь мы должны использовать имя переданного параметра. Конкретная модификация выглядит следующим образом:

private List<JsonRpcParam> getAnnotatedParameterNames(Method method) {

 List<JsonRpcParam> parameterNames = new ArrayList<>();

​

 List<Parameter> parameters = ReflectionUtil.getParameters(method);

 Iterator<Parameter> parameterIterator = parameters.iterator();

​

 List<String> parameterLocalNames = ReflectionUtil.getParameterLocalNames(method);

 Iterator<String> parameterLocalNameIterator = parameterLocalNames.iterator();

​

 while (parameterIterator.hasNext() && parameterLocalNameIterator.hasNext()) {

 parameterNames.add(getJsonRpcParamType(parameterIterator.next(), parameterLocalNameIterator.next()));

 }

 return parameterNames;

 }

​

 public static List<String> getParameterLocalNames(Method method) {

 List<String> parameterNames = new ArrayList<>();

 Collections.addAll(parameterNames, PARAMETER\_NAME\_DISCOVERER.getParameterNames(method));

 return Collections.unmodifiableList(parameterNames);

 }

Здесь мы в основном используем ParameterNameDiscoverer весной для получения имени параметра через байт-код, чтобы мы могли использовать метод передачи имени параметра без использования аннотаций.

Swagger

Swagger — это каноническая и полная структура для описания, создания, использования и визуализации веб-служб RESTful. Swagger на самом деле не очень подходит для такого рода вещей, но его также можно сгенерировать.Вы можете переписать структуру с открытым исходным кодом swagger-maven-plugin, чтобы сгенерировать свои собственные указанные.Однако, поскольку это используется только для быстрой отладки, часть чванства все еще временно Нет плана.

Суммировать

На этот раз я в основном расскажу, как конвертировать с thrfit на http. Необходимо добавить больше деталей, аутентификацию, распределенную систему отслеживания и т. д. Этот метод может быть не лучшим для реализации http. Я думаю, что лучший Остается реализовать rest, ведь rest распознается системными вызовами Интернета, но таким образом я научился преобразовывать из одного протокола в другой, и дополнил некоторые пробелы в преобразовании протоколов.

Справочная документация

спецификация jsonRpc2.0

Для получения дополнительных технических преимуществ, пожалуйста, отсканируйте общедоступную учетную запись ниже