Skip to content

Commit

Permalink
rework platform headers to use our standard plugin method (#264)
Browse files Browse the repository at this point in the history
Co-authored-by: Dean Hiller <[email protected]>
  • Loading branch information
deanhiller and deantray authored Mar 6, 2024
1 parent 5c59416 commit 9395f5a
Show file tree
Hide file tree
Showing 39 changed files with 508 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ public ClientServiceConfig(HeaderCtxList hcl, String serversName){
this(hcl, null, serversName);
}
public ClientServiceConfig(HeaderCtxList hcl, List<Class> successExceptions, String serviceName){
if(hcl == null) {
throw new IllegalArgumentException("Pass in a non-null hcl param please. We need the class still to determine which jar the application is in." +
" This will be deprecated in the future");
}
this.hcl = hcl;
if(hcl.listHeaderCtxPairs() != null) {
throw new IllegalArgumentException("listHeaderCtxPairs() must return null AND add this instead -> \n" +
"Multibinder<AddPlatformHeaders> htmlTagCreators = Multibinder.newSetBinder(binder, AddPlatformHeaders.class);\n" +
"htmlTagCreators.addBinding().to(CompanyHeaders.class);\n\n" +
"We now use binders instead so plugins can add headers they need");
}
this.successExceptions = successExceptions;
this.serversName = serviceName;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.webpieces.microsvc.server.api;

import org.webpieces.ctx.api.ClientServiceConfig;
import org.webpieces.util.context.AddPlatformHeaders;
import org.webpieces.util.context.Context;
import org.webpieces.util.context.PlatformHeaders;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

public class HeaderTranslation {

private final List<PlatformHeaders> listHeaders;

@Inject
public HeaderTranslation(
Set<AddPlatformHeaders> addPlatformHeaders,
ClientServiceConfig config
) {
listHeaders = new ArrayList<>();
for(AddPlatformHeaders add : addPlatformHeaders) {
Class<? extends PlatformHeaders> clazz = add.platformHeadersToAdd();
PlatformHeaders[] enumConstants = clazz.getEnumConstants();
List<PlatformHeaders> list = Arrays.asList(enumConstants);
listHeaders.addAll(list);
}

Context.checkForDuplicates(listHeaders);
}

public List<PlatformHeaders> getHeaders() {
return listHeaders;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.webpieces.microsvc.client.api;

import org.webpieces.util.context.AddPlatformHeaders;
import org.webpieces.util.context.PlatformHeaders;

public class AddGcpAuthHeaders implements AddPlatformHeaders {
@Override
public Class<? extends PlatformHeaders> platformHeadersToAdd() {
return GcpAuthHeader.class;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.webpieces.microsvc.client.api;

import org.webpieces.util.context.Context;

public interface Authentication {
public Object setupMagic();

public void resetMagic(Object state);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.webpieces.microsvc.client.api;

import org.webpieces.util.context.AddPlatformHeaders;
import org.webpieces.util.context.PlatformHeaders;

/**
*
*/
public enum GcpAuthHeader implements PlatformHeaders {

AUTH_TOKEN("Authorization", null, false, true),
METADATA_FLAVOR("Metadata-Flavor", null, false, false);

private final String headerName;
private final String logKey;
private final boolean isLog;
private final boolean isSecure;

GcpAuthHeader(String headerName, String logKey, boolean isLog, boolean isSecure) {
this.headerName = headerName;
this.logKey = logKey;
this.isLog = isLog;
this.isSecure = isSecure;
}


@Override
public String getHeaderName() {
return headerName;
}

@Override
public String getLoggerMDCKey() {
return logKey;
}

@Override
public boolean isWantLogged() {
return isLog;
}

@Override
public boolean isWantTransferred() {
return headerName != null;
}

@Override
public boolean isSecured() {
return isSecure;
}

@Override
public boolean isDimensionForMetrics() {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.webpieces.microsvc.client.api;

import org.webpieces.microsvc.client.impl.Endpoint;
import org.webpieces.microsvc.client.impl.HttpsJsonClient;
import org.webpieces.util.HostWithPort;
import org.webpieces.util.SneakyThrow;
import org.webpieces.util.context.Context;
import org.webpieces.util.futures.XFuture;

import javax.inject.Inject;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

public class GcpAuthentication implements Authentication {

private final HttpsJsonClient jsonClient;
private final Method method;
private long expiresAtSeconds = 0;
private String accessToken;

@Inject
public GcpAuthentication(
HttpsJsonClient jsonClient
) {
this.jsonClient = jsonClient;

try {
method = GcpAuthentication.class.getDeclaredMethod("fetchToken");
} catch (NoSuchMethodException e) {
throw SneakyThrow.sneak(e);
}
}

@Override
public Object setupMagic() {
String previousSetting = Context.getMagic(GcpAuthHeader.AUTH_TOKEN);

String accessToken = fetchToken();
Context.putMagic(GcpAuthHeader.AUTH_TOKEN, "Bearer "+accessToken);

return previousSetting;
}

private synchronized String fetchToken() {
long secondsEpochNow = System.currentTimeMillis() / 1000;
if(secondsEpochNow < expiresAtSeconds)
return accessToken;

try {
Context.putMagic(GcpAuthHeader.METADATA_FLAVOR, "Google");
HostWithPort host = new HostWithPort("metadata.google.internal", 80);
Endpoint endpoint = new Endpoint(host, "GET", "/computeMetadata/v1/instance/service-accounts/default/token");

XFuture<TokenResponse> mapXFuture = jsonClient.sendHttpRequest(method, null, endpoint, TokenResponse.class, true);
TokenResponse map = null;
try {
map = mapXFuture.get(20, TimeUnit.SECONDS);
} catch (Exception e) {
throw SneakyThrow.sneak(e);
}
accessToken = map.getAccessToken();
expiresAtSeconds = secondsEpochNow + map.getExpiresIn() - 30; //subtract 30 seconds so we renew before it expires

return accessToken;
} finally {
Context.removeMagic(GcpAuthHeader.METADATA_FLAVOR);
}
}

@Override
public void resetMagic(Object state) {
//restore to previous state or if state was null, remove magic..
if(state == null) {
Context.removeMagic(GcpAuthHeader.AUTH_TOKEN);
return;
}

String previousSetting = (String) state;
Context.putMagic(GcpAuthHeader.AUTH_TOKEN, previousSetting);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,29 @@ public RESTClientCreator(Provider<HttpsJsonClientInvokeHandler> wrapperProvider)
this.wrapperProvider = wrapperProvider;
}

public <T> T createAuthClient(Class<T> apiInterface, HostWithPort addr, Authentication auth) {
return createClient(apiInterface, addr, false, false, auth);
}

public <T> T createClient(Class<T> apiInterface, HostWithPort addr) {
return createClient(apiInterface, addr, false, false);
return createClient(apiInterface, addr, false, false, null);
}

public <T> T createClientPubsub(Class<T> apiInterface, HostWithPort addr) {
return createClient(apiInterface, addr, true, false);
return createClient(apiInterface, addr, true, false, null);
}

public <T> T createClientHttp(Class<T> apiInterface, HostWithPort addr) {
return createClient(apiInterface, addr, false, true);
return createClient(apiInterface, addr, false, true, null);
}

public <T> T createClient(Class<T> apiInterface, HostWithPort addr, boolean createForPubSub, boolean forHttp) {
public <T> T createClient(
Class<T> apiInterface,
HostWithPort addr,
boolean createForPubSub,
boolean forHttp,
Authentication authentication
) {

//quick DNS check or fail
try {
Expand All @@ -45,7 +55,7 @@ public <T> T createClient(Class<T> apiInterface, HostWithPort addr, boolean crea

HttpsJsonClientInvokeHandler invokeHandler = wrapperProvider.get();
boolean hasUrlParams = apiInterface.getAnnotation(NotEvolutionProof.class) != null;
invokeHandler.initialize(addr, hasUrlParams, forHttp);
invokeHandler.initialize(addr, hasUrlParams, forHttp, authentication);

boolean forceVoid = false;
if(createForPubSub)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.webpieces.microsvc.client.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class TokenResponse {
/*
{
"access_token":"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_QtAi85nHq39HE3C2LTrCARA",
"expires_in":3599,
"token_type":"Bearer"
}
*/

@JsonProperty("access_token")
private String accessToken;
@JsonProperty("expires_in")
private Long expiresIn;
@JsonProperty("token_type")
private String tokenType;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public Long getExpiresIn() {
return expiresIn;
}

public void setExpiresIn(Long expiresIn) {
this.expiresIn = expiresIn;
}

public String getTokenType() {
return tokenType;
}

public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.webpieces.microsvc.client.impl;

import org.webpieces.microsvc.client.api.Authentication;

public class EmptyAuth implements Authentication {
@Override
public Object setupMagic() {
return null;
}

@Override
public void resetMagic(Object state) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import org.webpieces.http2client.api.dto.FullResponse;
import org.webpieces.httpparser.api.common.KnownHeaderName;
import org.webpieces.microsvc.client.api.ClientSSLEngineFactory;
import org.webpieces.microsvc.server.api.HeaderTranslation;
import org.webpieces.util.HostWithPort;
import org.webpieces.plugin.json.JacksonJsonConverter;
import org.webpieces.plugin.json.JsonError;
import org.webpieces.util.context.AddPlatformHeaders;
import org.webpieces.util.context.Context;
import org.webpieces.util.context.PlatformHeaders;
import org.webpieces.util.exceptions.NioClosedChannelException;
Expand Down Expand Up @@ -79,26 +81,22 @@ public HttpsJsonClient(
FutureHelper futureUtil,
ScheduledExecutorService schedulerSvc,
Masker masker,
MeterRegistry metrics
MeterRegistry metrics,
HeaderTranslation translation
) {
this.sslFactory = sslFactory;
if(clientServiceConfig.getHcl() == null)
throw new IllegalArgumentException("clientServiceConfig.getHcl() cannot be null and was");

this.serversName = clientServiceConfig.getServersName();
this.metrics = metrics;

List<PlatformHeaders> listHeaders = clientServiceConfig.getHcl().listHeaderCtxPairs();

this.jsonMapper = jsonMapper;
this.client = client;
this.futureUtil = futureUtil;
this.schedulerSvc = schedulerSvc;
this.masker = masker;

Context.checkForDuplicates(listHeaders);
List<PlatformHeaders> headers = translation.getHeaders();

for(PlatformHeaders header : listHeaders) {
for(PlatformHeaders header : headers) {
if(header.isSecured()) {
secureList.add(header.getHeaderName());
}
Expand All @@ -110,6 +108,10 @@ public HttpsJsonClient(

}

private Enum something() {
return null;
}

private void cancel(Http2Socket clientSocket) {

try {
Expand Down Expand Up @@ -237,15 +239,15 @@ private <T> XFuture<T> sendAndTranslate(Method method, HostWithPort apiAddress,
//Creating a gauage in micrometer will have micrometer thread keep a reference to object forever.
//It is very important to do this lazy or we add a new AtomicInteger on every api call if it
//already exists instead of ONCE PR API CLASS (we do not even do once per method yet, though we could)
public AtomicInteger computeLazyToAvoidOOM(String key, AtomicInteger existing, Iterable<Tag> tags) {
private AtomicInteger computeLazyToAvoidOOM(String key, AtomicInteger existing, Iterable<Tag> tags) {
if(existing == null) {
existing = new AtomicInteger(0);
metrics.gauge("webpieces.requests.inflight", tags, existing, (c) -> c.get());
}
return existing;
}

public XFuture<FullResponse> send(Iterable<Tag> tags, AtomicInteger inFlightCounter, Http2Socket socket, FullRequest request) {
private XFuture<FullResponse> send(Iterable<Tag> tags, AtomicInteger inFlightCounter, Http2Socket socket, FullRequest request) {
XFuture<FullResponse> send = socket.send(request);

//in an ideal world, we would use the async send which can notify us when 'write' is done so it is out the door, but
Expand Down
Loading

0 comments on commit 9395f5a

Please sign in to comment.