mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 17:08:27 +01:00
initial grpc service code in chat
This commit is contained in:
committed by
GitHub
parent
cc3cab9c88
commit
8d995e456e
@@ -270,6 +270,11 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private TurnSecretConfiguration turn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private int grpcPort;
|
||||
|
||||
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
|
||||
return adminEventLoggingConfiguration;
|
||||
}
|
||||
@@ -448,4 +453,9 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
public TurnSecretConfiguration getTurnSecretConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public int getGrpcPort() {
|
||||
return grpcPort;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.binder.grpc.MetricCollectingServerInterceptor;
|
||||
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -105,6 +108,8 @@ import org.whispersystems.textsecuregcm.controllers.VerificationController;
|
||||
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
||||
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||
import org.whispersystems.textsecuregcm.grpc.GrpcServerManagedWrapper;
|
||||
import org.whispersystems.textsecuregcm.grpc.UserAgentInterceptor;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
|
||||
@@ -626,10 +631,22 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
AuthFilter<BasicCredentials, DisabledPermittedAuthenticatedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAuthenticatedAccount>().setAuthenticator(
|
||||
disabledPermittedAccountAuthenticator).buildAuthFilter();
|
||||
|
||||
final ServerBuilder<?> grpcServer = ServerBuilder.forPort(config.getGrpcPort())
|
||||
.intercept(new MetricCollectingServerInterceptor(Metrics.globalRegistry)); /* TODO: specialize metrics with user-agent platform */
|
||||
|
||||
RemoteDeprecationFilter remoteDeprecationFilter = new RemoteDeprecationFilter(dynamicConfigurationManager);
|
||||
environment.servlets()
|
||||
.addFilter("RemoteDeprecationFilter", new RemoteDeprecationFilter(dynamicConfigurationManager))
|
||||
.addFilter("RemoteDeprecationFilter", remoteDeprecationFilter)
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
|
||||
// Note: interceptors run in the reverse order they are added; the remote deprecation filter
|
||||
// depends on the user-agent context so it has to come first here!
|
||||
// http://grpc.github.io/grpc-java/javadoc/io/grpc/ServerBuilder.html#intercept-io.grpc.ServerInterceptor-
|
||||
grpcServer.intercept(remoteDeprecationFilter);
|
||||
grpcServer.intercept(new UserAgentInterceptor());
|
||||
|
||||
environment.lifecycle().manage(new GrpcServerManagedWrapper(grpcServer.build()));
|
||||
|
||||
environment.jersey().register(new RequestStatisticsFilter(TrafficSource.HTTP));
|
||||
environment.jersey().register(MultiRecipientMessageProvider.class);
|
||||
environment.jersey().register(new MetricsApplicationEventListener(TrafficSource.HTTP));
|
||||
|
||||
@@ -7,8 +7,15 @@ package org.whispersystems.textsecuregcm.filters;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.Status;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -22,6 +29,8 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRemoteDeprecationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.grpc.StatusConstants;
|
||||
import org.whispersystems.textsecuregcm.grpc.UserAgentInterceptor;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException;
|
||||
@@ -34,7 +43,7 @@ import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
|
||||
* If a client platform does not have a configured minimum version, all traffic from that client
|
||||
* platform is allowed.
|
||||
*/
|
||||
public class RemoteDeprecationFilter implements Filter {
|
||||
public class RemoteDeprecationFilter implements Filter, ServerInterceptor {
|
||||
|
||||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||
|
||||
@@ -52,60 +61,84 @@ public class RemoteDeprecationFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
final DynamicRemoteDeprecationConfiguration configuration = dynamicConfigurationManager
|
||||
.getConfiguration().getRemoteDeprecationConfiguration();
|
||||
|
||||
final Map<ClientPlatform, Semver> minimumVersionsByPlatform = configuration.getMinimumVersions();
|
||||
final Map<ClientPlatform, Semver> versionsPendingDeprecationByPlatform = configuration.getVersionsPendingDeprecation();
|
||||
final Map<ClientPlatform, Set<Semver>> blockedVersionsByPlatform = configuration.getBlockedVersions();
|
||||
final Map<ClientPlatform, Set<Semver>> versionsPendingBlockByPlatform = configuration.getVersionsPendingBlock();
|
||||
final boolean allowUnrecognizedUserAgents = configuration.isUnrecognizedUserAgentAllowed();
|
||||
|
||||
boolean shouldBlock = false;
|
||||
final String userAgentString = ((HttpServletRequest) request).getHeader(HttpHeaders.USER_AGENT);
|
||||
|
||||
UserAgent userAgent;
|
||||
try {
|
||||
final String userAgentString = ((HttpServletRequest) request).getHeader(HttpHeaders.USER_AGENT);
|
||||
final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
|
||||
|
||||
if (blockedVersionsByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (blockedVersionsByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) {
|
||||
recordDeprecation(userAgent, BLOCKED_CLIENT_REASON);
|
||||
shouldBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (minimumVersionsByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (userAgent.getVersion().isLowerThan(minimumVersionsByPlatform.get(userAgent.getPlatform()))) {
|
||||
recordDeprecation(userAgent, EXPIRED_CLIENT_REASON);
|
||||
shouldBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (versionsPendingBlockByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (versionsPendingBlockByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) {
|
||||
recordPendingDeprecation(userAgent, BLOCKED_CLIENT_REASON);
|
||||
}
|
||||
}
|
||||
|
||||
if (versionsPendingDeprecationByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (userAgent.getVersion().isLowerThan(versionsPendingDeprecationByPlatform.get(userAgent.getPlatform()))) {
|
||||
recordPendingDeprecation(userAgent, EXPIRED_CLIENT_REASON);
|
||||
}
|
||||
}
|
||||
userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
|
||||
} catch (final UnrecognizedUserAgentException e) {
|
||||
if (!allowUnrecognizedUserAgents) {
|
||||
recordDeprecation(null, UNRECOGNIZED_UA_REASON);
|
||||
shouldBlock = true;
|
||||
}
|
||||
userAgent = null;
|
||||
}
|
||||
|
||||
if (shouldBlock) {
|
||||
if (shouldBlock(userAgent)) {
|
||||
((HttpServletResponse) response).sendError(499);
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||
final ServerCall<ReqT, RespT> call,
|
||||
final Metadata headers,
|
||||
final ServerCallHandler<ReqT, RespT> next) {
|
||||
|
||||
if (shouldBlock(UserAgentUtil.userAgentFromGrpcContext())) {
|
||||
call.close(StatusConstants.UPGRADE_NEEDED_STATUS, new Metadata());
|
||||
return new ServerCall.Listener<>() {};
|
||||
} else {
|
||||
return next.startCall(call, headers);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldBlock(final UserAgent userAgent) {
|
||||
final DynamicRemoteDeprecationConfiguration configuration = dynamicConfigurationManager
|
||||
.getConfiguration().getRemoteDeprecationConfiguration();
|
||||
final Map<ClientPlatform, Semver> minimumVersionsByPlatform = configuration.getMinimumVersions();
|
||||
final Map<ClientPlatform, Semver> versionsPendingDeprecationByPlatform = configuration
|
||||
.getVersionsPendingDeprecation();
|
||||
final Map<ClientPlatform, Set<Semver>> blockedVersionsByPlatform = configuration.getBlockedVersions();
|
||||
final Map<ClientPlatform, Set<Semver>> versionsPendingBlockByPlatform = configuration.getVersionsPendingBlock();
|
||||
|
||||
boolean shouldBlock = false;
|
||||
|
||||
if (userAgent == null) {
|
||||
if (configuration.isUnrecognizedUserAgentAllowed()) {
|
||||
return false;
|
||||
}
|
||||
recordDeprecation(null, UNRECOGNIZED_UA_REASON);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (blockedVersionsByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (blockedVersionsByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) {
|
||||
recordDeprecation(userAgent, BLOCKED_CLIENT_REASON);
|
||||
shouldBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (minimumVersionsByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (userAgent.getVersion().isLowerThan(minimumVersionsByPlatform.get(userAgent.getPlatform()))) {
|
||||
recordDeprecation(userAgent, EXPIRED_CLIENT_REASON);
|
||||
shouldBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (versionsPendingBlockByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (versionsPendingBlockByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) {
|
||||
recordPendingDeprecation(userAgent, BLOCKED_CLIENT_REASON);
|
||||
}
|
||||
}
|
||||
|
||||
if (versionsPendingDeprecationByPlatform.containsKey(userAgent.getPlatform())) {
|
||||
if (userAgent.getVersion().isLowerThan(versionsPendingDeprecationByPlatform.get(userAgent.getPlatform()))) {
|
||||
recordPendingDeprecation(userAgent, EXPIRED_CLIENT_REASON);
|
||||
}
|
||||
}
|
||||
|
||||
return shouldBlock;
|
||||
}
|
||||
|
||||
private void recordDeprecation(final UserAgent userAgent, final String reason) {
|
||||
Metrics.counter(DEPRECATED_CLIENT_COUNTER_NAME,
|
||||
PLATFORM_TAG, userAgent != null ? userAgent.getPlatform().name().toLowerCase() : "unrecognized",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import io.grpc.Server;
|
||||
|
||||
public class GrpcServerManagedWrapper implements Managed {
|
||||
|
||||
private final Server server;
|
||||
|
||||
public GrpcServerManagedWrapper(final Server server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws IOException {
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
try {
|
||||
server.shutdown().awaitTermination(5, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
server.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.Status;
|
||||
|
||||
public abstract class StatusConstants {
|
||||
public static final Status UPGRADE_NEEDED_STATUS = Status.INVALID_ARGUMENT.withDescription("signal-upgrade-required");
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException;
|
||||
import org.whispersystems.textsecuregcm.util.ua.UserAgent;
|
||||
import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
|
||||
|
||||
import io.grpc.Context;
|
||||
import io.grpc.Contexts;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.Status;
|
||||
|
||||
public class UserAgentInterceptor implements ServerInterceptor {
|
||||
@VisibleForTesting
|
||||
public static final Metadata.Key<String> USER_AGENT_GRPC_HEADER =
|
||||
Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call,
|
||||
final Metadata headers,
|
||||
final ServerCallHandler<ReqT, RespT> next) {
|
||||
|
||||
UserAgent userAgent;
|
||||
try {
|
||||
userAgent = UserAgentUtil.parseUserAgentString(headers.get(USER_AGENT_GRPC_HEADER));
|
||||
} catch (final UnrecognizedUserAgentException e) {
|
||||
userAgent = null;
|
||||
}
|
||||
|
||||
final Context context = Context.current().withValue(UserAgentUtil.USER_AGENT_CONTEXT_KEY, userAgent);
|
||||
return Contexts.interceptCall(context, call, headers, next);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* Copyright 2013-2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.util.ua;
|
||||
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UserAgent {
|
||||
|
||||
private final ClientPlatform platform;
|
||||
private final Semver version;
|
||||
private final String additionalSpecifiers;
|
||||
private final ClientPlatform platform;
|
||||
private final Semver version;
|
||||
private final String additionalSpecifiers;
|
||||
|
||||
public UserAgent(final ClientPlatform platform, final Semver version) {
|
||||
this(platform, version, null);
|
||||
}
|
||||
public UserAgent(final ClientPlatform platform, final Semver version) {
|
||||
this(platform, version, null);
|
||||
}
|
||||
|
||||
public UserAgent(final ClientPlatform platform, final Semver version, final String additionalSpecifiers) {
|
||||
this.platform = platform;
|
||||
this.version = version;
|
||||
this.additionalSpecifiers = additionalSpecifiers;
|
||||
}
|
||||
public UserAgent(final ClientPlatform platform, final Semver version, final String additionalSpecifiers) {
|
||||
this.platform = platform;
|
||||
this.version = version;
|
||||
this.additionalSpecifiers = additionalSpecifiers;
|
||||
}
|
||||
|
||||
public ClientPlatform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
public ClientPlatform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public Semver getVersion() {
|
||||
return version;
|
||||
}
|
||||
public Semver getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Optional<String> getAdditionalSpecifiers() {
|
||||
return Optional.ofNullable(additionalSpecifiers);
|
||||
}
|
||||
public Optional<String> getAdditionalSpecifiers() {
|
||||
return Optional.ofNullable(additionalSpecifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final UserAgent userAgent = (UserAgent)o;
|
||||
return platform == userAgent.platform &&
|
||||
version.equals(userAgent.version) &&
|
||||
Objects.equals(additionalSpecifiers, userAgent.additionalSpecifiers);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final UserAgent userAgent = (UserAgent)o;
|
||||
return platform == userAgent.platform &&
|
||||
version.equals(userAgent.version) &&
|
||||
Objects.equals(additionalSpecifiers, userAgent.additionalSpecifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(platform, version, additionalSpecifiers);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(platform, version, additionalSpecifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserAgent{" +
|
||||
"platform=" + platform +
|
||||
", version=" + version +
|
||||
", additionalSpecifiers='" + additionalSpecifiers + '\'' +
|
||||
'}';
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserAgent{" +
|
||||
"platform=" + platform +
|
||||
", version=" + version +
|
||||
", additionalSpecifiers='" + additionalSpecifiers + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,40 +7,47 @@ package org.whispersystems.textsecuregcm.util.ua;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import io.grpc.Context;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class UserAgentUtil {
|
||||
|
||||
private static final Pattern STANDARD_UA_PATTERN = Pattern.compile("^Signal-(Android|Desktop|iOS)/([^ ]+)( (.+))?$", Pattern.CASE_INSENSITIVE);
|
||||
public static final Context.Key<UserAgent> USER_AGENT_CONTEXT_KEY = Context.key("x-signal-user-agent");
|
||||
|
||||
public static UserAgent parseUserAgentString(final String userAgentString) throws UnrecognizedUserAgentException {
|
||||
if (StringUtils.isBlank(userAgentString)) {
|
||||
throw new UnrecognizedUserAgentException("User-Agent string is blank");
|
||||
}
|
||||
private static final Pattern STANDARD_UA_PATTERN = Pattern.compile("^Signal-(Android|Desktop|iOS)/([^ ]+)( (.+))?$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
try {
|
||||
final UserAgent standardUserAgent = parseStandardUserAgentString(userAgentString);
|
||||
|
||||
if (standardUserAgent != null) {
|
||||
return standardUserAgent;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
throw new UnrecognizedUserAgentException(e);
|
||||
}
|
||||
|
||||
throw new UnrecognizedUserAgentException();
|
||||
public static UserAgent parseUserAgentString(final String userAgentString) throws UnrecognizedUserAgentException {
|
||||
if (StringUtils.isBlank(userAgentString)) {
|
||||
throw new UnrecognizedUserAgentException("User-Agent string is blank");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static UserAgent parseStandardUserAgentString(final String userAgentString) {
|
||||
final Matcher matcher = STANDARD_UA_PATTERN.matcher(userAgentString);
|
||||
try {
|
||||
final UserAgent standardUserAgent = parseStandardUserAgentString(userAgentString);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return new UserAgent(ClientPlatform.valueOf(matcher.group(1).toUpperCase()), new Semver(matcher.group(2)), StringUtils.stripToNull(matcher.group(4)));
|
||||
}
|
||||
|
||||
return null;
|
||||
if (standardUserAgent != null) {
|
||||
return standardUserAgent;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
throw new UnrecognizedUserAgentException(e);
|
||||
}
|
||||
|
||||
throw new UnrecognizedUserAgentException();
|
||||
}
|
||||
|
||||
public static UserAgent userAgentFromGrpcContext() {
|
||||
return USER_AGENT_CONTEXT_KEY.get();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static UserAgent parseStandardUserAgentString(final String userAgentString) {
|
||||
final Matcher matcher = STANDARD_UA_PATTERN.matcher(userAgentString);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return new UserAgent(ClientPlatform.valueOf(matcher.group(1).toUpperCase()), new Semver(matcher.group(2)), StringUtils.stripToNull(matcher.group(4)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user