mirror of
https://github.com/google/nomulus
synced 2026-06-09 08:22:59 +00:00
Use built-in Java URL connections instead of UrlFetchService (#1535)
- Use the standard HttpsURLConnection to write/read data - Rewrite RdeReporter, Nordn*Action, and Marksdb classes and related tests to conform to the new format - Remove FakeURLFetchService and ForwardingUrlFetchService as they weren't used - Refactor UrlFetchException to UrlConnectionException - Refactor UrlFetchUtils to UrlConnectionUtils I will need to test this on Alpha. Fortunately the connections that don't require auth (e.g. TMDB downloading) should be testable.
This commit is contained in:
@@ -43,7 +43,7 @@ import google.registry.rde.JSchModule;
|
||||
import google.registry.request.Modules.DatastoreServiceModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
import google.registry.request.Modules.URLFetchServiceModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.request.auth.AuthModule;
|
||||
@@ -80,7 +80,7 @@ import javax.inject.Singleton;
|
||||
ServerTridProviderModule.class,
|
||||
SheetsServiceModule.class,
|
||||
StackdriverModule.class,
|
||||
URLFetchServiceModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UrlFetchTransportModule.class,
|
||||
UserServiceModule.class,
|
||||
VoidDnsWriterModule.class,
|
||||
|
||||
@@ -34,7 +34,6 @@ import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.request.auth.AuthModule;
|
||||
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
|
||||
@@ -65,7 +64,6 @@ import javax.inject.Singleton;
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
UrlFetchTransportModule.class,
|
||||
UserServiceModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
|
||||
@@ -34,7 +34,6 @@ import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.request.auth.AuthModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
@@ -62,7 +61,6 @@ import javax.inject.Singleton;
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
UrlFetchTransportModule.class,
|
||||
UserServiceModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
|
||||
@@ -36,7 +36,6 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.request.Modules.DatastoreServiceModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.request.auth.AuthModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
@@ -66,7 +65,6 @@ import javax.inject.Singleton;
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
ToolsRequestComponentModule.class,
|
||||
UrlFetchTransportModule.class,
|
||||
UserServiceModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
|
||||
@@ -14,26 +14,24 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.PUT;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static google.registry.request.UrlConnectionUtils.setBasicAuth;
|
||||
import static google.registry.request.UrlConnectionUtils.setPayload;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.api.client.http.HttpMethods;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.request.HttpException.InternalServerErrorException;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.UrlFetchException;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import google.registry.xjc.XjcXmlTransformer;
|
||||
import google.registry.xjc.iirdea.XjcIirdeaResponseElement;
|
||||
import google.registry.xjc.iirdea.XjcIirdeaResult;
|
||||
@@ -41,6 +39,7 @@ import google.registry.xjc.rdeheader.XjcRdeHeader;
|
||||
import google.registry.xjc.rdereport.XjcRdeReportReport;
|
||||
import google.registry.xml.XmlException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
@@ -55,12 +54,15 @@ public class RdeReporter {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** @see <a href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4">
|
||||
* ICANN Registry Interfaces - Interface details</a>*/
|
||||
private static final String REPORT_MIME = "text/xml";
|
||||
/**
|
||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4">
|
||||
* ICANN Registry Interfaces - Interface details</a>
|
||||
*/
|
||||
private static final MediaType MEDIA_TYPE = MediaType.XML_UTF_8;
|
||||
|
||||
@Inject Retrier retrier;
|
||||
@Inject URLFetchService urlFetchService;
|
||||
@Inject UrlConnectionService urlConnectionService;
|
||||
|
||||
@Inject @Config("rdeReportUrlPrefix") String reportUrlPrefix;
|
||||
@Inject @Key("icannReportingPassword") String password;
|
||||
@Inject RdeReporter() {}
|
||||
@@ -74,29 +76,24 @@ public class RdeReporter {
|
||||
// Send a PUT request to ICANN's HTTPS server.
|
||||
URL url = makeReportUrl(header.getTld(), report.getId());
|
||||
String username = header.getTld() + "_ry";
|
||||
String token = base64().encode(String.format("%s:%s", username, password).getBytes(UTF_8));
|
||||
final HTTPRequest req = new HTTPRequest(url, PUT, validateCertificate().setDeadline(60d));
|
||||
req.addHeader(new HTTPHeader(CONTENT_TYPE, REPORT_MIME));
|
||||
req.addHeader(new HTTPHeader(AUTHORIZATION, "Basic " + token));
|
||||
req.setPayload(reportBytes);
|
||||
logger.atInfo().log("Sending report:\n%s", new String(reportBytes, UTF_8));
|
||||
HTTPResponse rsp =
|
||||
byte[] responseBytes =
|
||||
retrier.callWithRetry(
|
||||
() -> {
|
||||
HTTPResponse rsp1 = urlFetchService.fetch(req);
|
||||
switch (rsp1.getResponseCode()) {
|
||||
case SC_OK:
|
||||
case SC_BAD_REQUEST:
|
||||
break;
|
||||
default:
|
||||
throw new UrlFetchException("PUT failed", req, rsp1);
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(url);
|
||||
connection.setRequestMethod(HttpMethods.PUT);
|
||||
setBasicAuth(connection, username, password);
|
||||
setPayload(connection, reportBytes, MEDIA_TYPE.toString());
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == SC_OK || responseCode == SC_BAD_REQUEST) {
|
||||
return getResponseBytes(connection);
|
||||
}
|
||||
return rsp1;
|
||||
throw new UrlConnectionException("PUT failed", connection);
|
||||
},
|
||||
SocketTimeoutException.class);
|
||||
|
||||
// Ensure the XML response is valid.
|
||||
XjcIirdeaResult result = parseResult(rsp);
|
||||
XjcIirdeaResult result = parseResult(responseBytes);
|
||||
if (result.getCode().getValue() != 1000) {
|
||||
logger.atWarning().log(
|
||||
"PUT rejected: %d %s\n%s",
|
||||
@@ -108,11 +105,11 @@ public class RdeReporter {
|
||||
/**
|
||||
* Unmarshals IIRDEA XML result object from {@link HTTPResponse} payload.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4.1">
|
||||
* @see <a
|
||||
* href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4.1">
|
||||
* ICANN Registry Interfaces - IIRDEA Result Object</a>
|
||||
*/
|
||||
private XjcIirdeaResult parseResult(HTTPResponse rsp) throws XmlException {
|
||||
byte[] responseBytes = rsp.getContent();
|
||||
private XjcIirdeaResult parseResult(byte[] responseBytes) throws XmlException {
|
||||
logger.atInfo().log("Received response:\n%s", new String(responseBytes, UTF_8));
|
||||
XjcIirdeaResponseElement response = XjcXmlTransformer.unmarshal(
|
||||
XjcIirdeaResponseElement.class, new ByteArrayInputStream(responseBytes));
|
||||
|
||||
@@ -23,12 +23,11 @@ import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.appengine.api.datastore.DatastoreService;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import java.net.HttpURLConnection;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Dagger modules for App Engine services and other vendor classes. */
|
||||
@@ -45,14 +44,12 @@ public final class Modules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Dagger module for {@link URLFetchService}. */
|
||||
/** Dagger module for {@link UrlConnectionService}. */
|
||||
@Module
|
||||
public static final class URLFetchServiceModule {
|
||||
private static final URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
|
||||
|
||||
public static final class UrlConnectionServiceModule {
|
||||
@Provides
|
||||
static URLFetchService provideURLFetchService() {
|
||||
return fetchService;
|
||||
static UrlConnectionService provideUrlConnectionService() {
|
||||
return url -> (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/** Functional interface for opening a connection from a URL, injectable for testing. */
|
||||
public interface UrlConnectionService {
|
||||
|
||||
HttpURLConnection createConnection(URL url) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.request;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.MediaType;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Random;
|
||||
|
||||
/** Utilities for common functionality relating to {@link java.net.URLConnection}s. */
|
||||
public class UrlConnectionUtils {
|
||||
|
||||
/** Retrieves the response from the given connection as a byte array. */
|
||||
public static byte[] getResponseBytes(URLConnection connection) throws IOException {
|
||||
return ByteStreams.toByteArray(connection.getInputStream());
|
||||
}
|
||||
|
||||
/** Sets auth on the given connection with the given username/password. */
|
||||
public static void setBasicAuth(URLConnection connection, String username, String password) {
|
||||
setBasicAuth(connection, String.format("%s:%s", username, password));
|
||||
}
|
||||
|
||||
/** Sets auth on the given connection with the given string, formatted "username:password". */
|
||||
public static void setBasicAuth(URLConnection connection, String usernameAndPassword) {
|
||||
String token = base64().encode(usernameAndPassword.getBytes(UTF_8));
|
||||
connection.setRequestProperty(AUTHORIZATION, "Basic " + token);
|
||||
}
|
||||
|
||||
/** Sets the given byte[] payload on the given connection with a particular content type. */
|
||||
public static void setPayload(URLConnection connection, byte[] bytes, String contentType)
|
||||
throws IOException {
|
||||
connection.setRequestProperty(CONTENT_TYPE, contentType);
|
||||
connection.setDoOutput(true);
|
||||
try (DataOutputStream dataStream = new DataOutputStream(connection.getOutputStream())) {
|
||||
dataStream.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets payload on request as a {@code multipart/form-data} request.
|
||||
*
|
||||
* <p>This is equivalent to running the command: {@code curl -F fieldName=@payload.txt URL}
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2388.txt">RFC2388 - Returning Values from Forms</a>
|
||||
*/
|
||||
public static void setPayloadMultipart(
|
||||
URLConnection connection,
|
||||
String name,
|
||||
String filename,
|
||||
MediaType contentType,
|
||||
String data,
|
||||
Random random)
|
||||
throws IOException {
|
||||
String boundary = createMultipartBoundary(random);
|
||||
checkState(
|
||||
!data.contains(boundary), "Multipart data contains autogenerated boundary: %s", boundary);
|
||||
String multipart =
|
||||
String.format("--%s\r\n", boundary)
|
||||
+ String.format(
|
||||
"%s: form-data; name=\"%s\"; filename=\"%s\"\r\n",
|
||||
CONTENT_DISPOSITION, name, filename)
|
||||
+ String.format("%s: %s\r\n", CONTENT_TYPE, contentType)
|
||||
+ "\r\n"
|
||||
+ data
|
||||
+ "\r\n"
|
||||
+ String.format("--%s--\r\n", boundary);
|
||||
byte[] payload = multipart.getBytes(UTF_8);
|
||||
connection.setRequestProperty(CONTENT_LENGTH, Integer.toString(payload.length));
|
||||
setPayload(
|
||||
connection, payload, String.format("multipart/form-data;" + " boundary=\"%s\"", boundary));
|
||||
}
|
||||
|
||||
private static String createMultipartBoundary(Random random) {
|
||||
// Generate 192 random bits (24 bytes) to produce 192/log_2(64) = 192/6 = 32 base64 digits.
|
||||
byte[] rand = new byte[24];
|
||||
random.nextBytes(rand);
|
||||
// Boundary strings can be up to 70 characters long, so use 30 hyphens plus 32 random digits.
|
||||
// See https://tools.ietf.org/html/rfc2046#section-5.1.1
|
||||
return Strings.repeat("-", 30) + base64().encode(rand);
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,12 @@
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Verify.verifyNotNull;
|
||||
import static google.registry.util.UrlFetchUtils.setAuthorizationHeader;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.UrlConnectionUtils;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -37,8 +37,9 @@ final class LordnRequestInitializer {
|
||||
}
|
||||
|
||||
/** Initializes a URL fetch request for talking to the MarksDB server. */
|
||||
void initialize(HTTPRequest request, String tld) {
|
||||
setAuthorizationHeader(request, getMarksDbLordnCredentials(tld));
|
||||
void initialize(HttpURLConnection connection, String tld) {
|
||||
getMarksDbLordnCredentials(tld)
|
||||
.ifPresent(login -> UrlConnectionUtils.setBasicAuth(connection, login));
|
||||
}
|
||||
|
||||
/** Returns the username and password for the current TLD to login to the MarksDB server. */
|
||||
|
||||
@@ -14,25 +14,23 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.GET;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static google.registry.request.UrlConnectionUtils.setBasicAuth;
|
||||
import static google.registry.util.HexDumper.dumpHex;
|
||||
import static google.registry.util.UrlFetchUtils.setAuthorizationHeader;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteSource;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.util.UrlFetchException;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
@@ -57,7 +55,8 @@ public final class Marksdb {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final int MAX_DNL_LOGGING_LENGTH = 500;
|
||||
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject UrlConnectionService urlConnectionService;
|
||||
|
||||
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
||||
@Inject @Key("marksdbPublicKey") PGPPublicKey marksdbPublicKey;
|
||||
@Inject Marksdb() {}
|
||||
@@ -112,19 +111,16 @@ public final class Marksdb {
|
||||
}
|
||||
|
||||
byte[] fetch(URL url, Optional<String> loginAndPassword) throws IOException {
|
||||
HTTPRequest req = new HTTPRequest(url, GET, validateCertificate().setDeadline(60d));
|
||||
setAuthorizationHeader(req, loginAndPassword);
|
||||
HTTPResponse rsp;
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(url);
|
||||
loginAndPassword.ifPresent(auth -> setBasicAuth(connection, auth));
|
||||
try {
|
||||
rsp = fetchService.fetch(req);
|
||||
if (connection.getResponseCode() != SC_OK) {
|
||||
throw new UrlConnectionException("Failed to fetch from MarksDB", connection);
|
||||
}
|
||||
return getResponseBytes(connection);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(
|
||||
String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
throw new IOException(String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
}
|
||||
if (rsp.getResponseCode() != SC_OK) {
|
||||
throw new UrlFetchException("Failed to fetch from MarksDB", req, rsp);
|
||||
}
|
||||
return rsp.getContent();
|
||||
}
|
||||
|
||||
List<String> fetchSignedCsv(Optional<String> loginAndPassword, String csvPath, String sigPath)
|
||||
|
||||
@@ -16,28 +16,23 @@ package google.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.POST;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.CSV_UTF_8;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static google.registry.tmch.LordnTaskUtils.COLUMNS_CLAIMS;
|
||||
import static google.registry.tmch.LordnTaskUtils.COLUMNS_SUNRISE;
|
||||
import static google.registry.util.UrlFetchUtils.getHeaderFirst;
|
||||
import static google.registry.util.UrlFetchUtils.setPayloadMultipart;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||
|
||||
import com.google.api.client.http.HttpMethods;
|
||||
import com.google.appengine.api.taskqueue.LeaseOptions;
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.apphosting.api.DeadlineExceededException;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
@@ -49,16 +44,18 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.request.UrlConnectionUtils;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.util.UrlFetchException;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
@@ -97,7 +94,8 @@ public final class NordnUploadAction implements Runnable {
|
||||
@Inject Retrier retrier;
|
||||
@Inject SecureRandom random;
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject UrlConnectionService urlConnectionService;
|
||||
|
||||
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
||||
@Inject @Parameter(LORDN_PHASE_PARAM) String phase;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@@ -193,47 +191,48 @@ public final class NordnUploadAction implements Runnable {
|
||||
* <p>Idempotency: If the exact same LORDN report is uploaded twice, the MarksDB server will
|
||||
* return the same confirmation number.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3">
|
||||
* TMCH functional specifications - LORDN File</a>
|
||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3">TMCH
|
||||
* functional specifications - LORDN File</a>
|
||||
*/
|
||||
private void uploadCsvToLordn(String urlPath, String csvData) throws IOException {
|
||||
String url = tmchMarksdbUrl + urlPath;
|
||||
logger.atInfo().log(
|
||||
"LORDN upload task %s: Sending to URL: %s ; data: %s", actionLogId, url, csvData);
|
||||
HTTPRequest req = new HTTPRequest(new URL(url), POST, validateCertificate().setDeadline(60d));
|
||||
lordnRequestInitializer.initialize(req, tld);
|
||||
setPayloadMultipart(req, "file", "claims.csv", CSV_UTF_8, csvData, random);
|
||||
HTTPResponse rsp;
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(new URL(url));
|
||||
connection.setRequestMethod(HttpMethods.POST);
|
||||
lordnRequestInitializer.initialize(connection, tld);
|
||||
UrlConnectionUtils.setPayloadMultipart(
|
||||
connection, "file", "claims.csv", CSV_UTF_8, csvData, random);
|
||||
try {
|
||||
rsp = fetchService.fetch(req);
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (logger.atInfo().isEnabled()) {
|
||||
String responseContent = new String(getResponseBytes(connection), US_ASCII);
|
||||
if (responseContent.isEmpty()) {
|
||||
responseContent = "(null)";
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"LORDN upload task %s response: HTTP response code %d, response data: %s",
|
||||
actionLogId, responseCode, responseContent);
|
||||
}
|
||||
if (responseCode != SC_ACCEPTED) {
|
||||
throw new UrlConnectionException(
|
||||
String.format(
|
||||
"LORDN upload task %s error: Failed to upload LORDN claims to MarksDB",
|
||||
actionLogId),
|
||||
connection);
|
||||
}
|
||||
String location = connection.getHeaderField(LOCATION);
|
||||
if (location == null) {
|
||||
throw new UrlConnectionException(
|
||||
String.format(
|
||||
"LORDN upload task %s error: MarksDB failed to provide a Location header",
|
||||
actionLogId),
|
||||
connection);
|
||||
}
|
||||
getQueue(NordnVerifyAction.QUEUE).add(makeVerifyTask(new URL(location)));
|
||||
} catch (IOException e) {
|
||||
throw new IOException(
|
||||
String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
throw new IOException(String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
}
|
||||
if (logger.atInfo().isEnabled()) {
|
||||
String response =
|
||||
(rsp.getContent() == null) ? "(null)" : new String(rsp.getContent(), US_ASCII);
|
||||
logger.atInfo().log(
|
||||
"LORDN upload task %s response: HTTP response code %d, response data: %s",
|
||||
actionLogId, rsp.getResponseCode(), response);
|
||||
}
|
||||
if (rsp.getResponseCode() != SC_ACCEPTED) {
|
||||
throw new UrlFetchException(
|
||||
String.format(
|
||||
"LORDN upload task %s error: Failed to upload LORDN claims to MarksDB", actionLogId),
|
||||
req,
|
||||
rsp);
|
||||
}
|
||||
Optional<String> location = getHeaderFirst(rsp, LOCATION);
|
||||
if (!location.isPresent()) {
|
||||
throw new UrlFetchException(
|
||||
String.format(
|
||||
"LORDN upload task %s error: MarksDB failed to provide a Location header",
|
||||
actionLogId),
|
||||
req,
|
||||
rsp);
|
||||
}
|
||||
getQueue(NordnVerifyAction.QUEUE).add(makeVerifyTask(new URL(location.get())));
|
||||
}
|
||||
|
||||
private TaskOptions makeVerifyTask(URL url) {
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.GET;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteSource;
|
||||
@@ -32,9 +28,11 @@ import google.registry.request.HttpException.ConflictException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.UrlFetchException;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Map.Entry;
|
||||
import javax.inject.Inject;
|
||||
@@ -68,7 +66,8 @@ public final class NordnVerifyAction implements Runnable {
|
||||
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@Inject Response response;
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject UrlConnectionService urlConnectionService;
|
||||
|
||||
@Inject @Header(URL_HEADER) URL url;
|
||||
@Inject @Header(HEADER_ACTION_LOG_ID) String actionLogId;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@@ -96,51 +95,49 @@ public final class NordnVerifyAction implements Runnable {
|
||||
@VisibleForTesting
|
||||
LordnLog verify() throws IOException {
|
||||
logger.atInfo().log("LORDN verify task %s: Sending request to URL %s", actionLogId, url);
|
||||
HTTPRequest req = new HTTPRequest(url, GET, validateCertificate().setDeadline(60d));
|
||||
lordnRequestInitializer.initialize(req, tld);
|
||||
HTTPResponse rsp;
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(url);
|
||||
lordnRequestInitializer.initialize(connection, tld);
|
||||
try {
|
||||
rsp = fetchService.fetch(req);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(
|
||||
String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"LORDN verify task %s response: HTTP response code %d, response data: %s",
|
||||
actionLogId, rsp.getResponseCode(), rsp.getContent());
|
||||
if (rsp.getResponseCode() == SC_NO_CONTENT) {
|
||||
// Send a 400+ status code so App Engine will retry the task.
|
||||
throw new ConflictException("Not ready");
|
||||
}
|
||||
if (rsp.getResponseCode() != SC_OK) {
|
||||
throw new UrlFetchException(
|
||||
String.format("LORDN verify task %s: Failed to verify LORDN upload to MarksDB.",
|
||||
actionLogId),
|
||||
req, rsp);
|
||||
}
|
||||
LordnLog log =
|
||||
LordnLog.parse(ByteSource.wrap(rsp.getContent()).asCharSource(UTF_8).readLines());
|
||||
if (log.getStatus() == LordnLog.Status.ACCEPTED) {
|
||||
logger.atInfo().log("LORDN verify task %s: Upload accepted.", actionLogId);
|
||||
} else {
|
||||
logger.atSevere().log(
|
||||
"LORDN verify task %s: Upload rejected with reason: %s", actionLogId, log);
|
||||
}
|
||||
for (Entry<String, LordnLog.Result> result : log) {
|
||||
switch (result.getValue().getOutcome()) {
|
||||
case OK:
|
||||
break;
|
||||
case WARNING:
|
||||
// fall through
|
||||
case ERROR:
|
||||
logger.atWarning().log(result.toString());
|
||||
break;
|
||||
default:
|
||||
logger.atWarning().log(
|
||||
"LORDN verify task %s: Unexpected outcome: %s", actionLogId, result);
|
||||
break;
|
||||
int responseCode = connection.getResponseCode();
|
||||
logger.atInfo().log(
|
||||
"LORDN verify task %s response: HTTP response code %d", actionLogId, responseCode);
|
||||
if (responseCode == SC_NO_CONTENT) {
|
||||
// Send a 400+ status code so App Engine will retry the task.
|
||||
throw new ConflictException("Not ready");
|
||||
}
|
||||
if (responseCode != SC_OK) {
|
||||
throw new UrlConnectionException(
|
||||
String.format(
|
||||
"LORDN verify task %s: Failed to verify LORDN upload to MarksDB.", actionLogId),
|
||||
connection);
|
||||
}
|
||||
LordnLog log =
|
||||
LordnLog.parse(
|
||||
ByteSource.wrap(getResponseBytes(connection)).asCharSource(UTF_8).readLines());
|
||||
if (log.getStatus() == LordnLog.Status.ACCEPTED) {
|
||||
logger.atInfo().log("LORDN verify task %s: Upload accepted.", actionLogId);
|
||||
} else {
|
||||
logger.atSevere().log(
|
||||
"LORDN verify task %s: Upload rejected with reason: %s", actionLogId, log);
|
||||
}
|
||||
for (Entry<String, LordnLog.Result> result : log) {
|
||||
switch (result.getValue().getOutcome()) {
|
||||
case OK:
|
||||
break;
|
||||
case WARNING:
|
||||
// fall through
|
||||
case ERROR:
|
||||
logger.atWarning().log(result.toString());
|
||||
break;
|
||||
default:
|
||||
logger.atWarning().log(
|
||||
"LORDN verify task %s: Unexpected outcome: %s", actionLogId, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return log;
|
||||
} catch (IOException e) {
|
||||
throw new IOException(String.format("Error connecting to MarksDB at URL %s", url), e);
|
||||
}
|
||||
return log;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.rde.RdeModule;
|
||||
import google.registry.request.Modules.DatastoreServiceModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.URLFetchServiceModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.tools.AuthModule.LocalCredentialModule;
|
||||
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
|
||||
@@ -79,8 +78,7 @@ import javax.inject.Singleton;
|
||||
RegistryToolDataflowModule.class,
|
||||
RequestFactoryModule.class,
|
||||
SecretManagerModule.class,
|
||||
URLFetchServiceModule.class,
|
||||
UrlFetchTransportModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UserServiceModule.class,
|
||||
UtilsModule.class,
|
||||
VoidDnsWriterModule.class,
|
||||
|
||||
Reference in New Issue
Block a user