1
1
mirror of https://github.com/MarginaliaSearch/MarginaliaSearch.git synced 2025-10-06 07:32:38 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Viktor Lofgren
42bd79a609 (crawler) Experimentally throttle the number of active retrievals to see how this affects the network performance
There's been some indications that request storms lead to buffer bloat and bad throughput.

This adds a configurable semaphore, by default permitting 100 active requests.
2025-03-22 11:50:37 +01:00
Viktor Lofgren
b91c1e528a (favicon) Send dummy svg result when image is missing
This prevents the browser from rendering a "broken image" in this scenario.
2025-03-21 15:15:14 +01:00
2 changed files with 68 additions and 32 deletions

View File

@@ -30,6 +30,7 @@ import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.zip.GZIPInputStream;
@@ -116,7 +117,7 @@ public class HttpFetcherImpl implements HttpFetcher {
for (int tries = 0;; tries++) {
try {
var rsp = client.send(head, HttpResponse.BodyHandlers.discarding());
var rsp = SendLock.wrapSend(client, head, HttpResponse.BodyHandlers.discarding());
EdgeUrl rspUri = new EdgeUrl(rsp.uri());
if (!Objects.equals(rspUri.domain, url.domain)) {
@@ -153,7 +154,7 @@ public class HttpFetcherImpl implements HttpFetcher {
.timeout(requestTimeout)
;
var rsp = client.send(headBuilder.build(), HttpResponse.BodyHandlers.discarding());
var rsp = SendLock.wrapSend(client, headBuilder.build(), HttpResponse.BodyHandlers.discarding());
var headers = rsp.headers();
var contentTypeHeader = headers.firstValue("Content-Type").orElse(null);
@@ -229,6 +230,7 @@ public class HttpFetcherImpl implements HttpFetcher {
contentTags.paint(getBuilder);
try (var sl = new SendLock()) {
HttpFetchResult result = warcRecorder.fetch(client, getBuilder.build());
if (result instanceof HttpFetchResult.ResultOk ok) {
@@ -246,6 +248,8 @@ public class HttpFetcherImpl implements HttpFetcher {
return result;
}
}
@Override
public SitemapRetriever createSitemapRetriever() {
return new SitemapRetriever();
@@ -317,22 +321,28 @@ public class HttpFetcherImpl implements HttpFetcher {
.timeout(requestTimeout)
.build();
try (var sl = new SendLock()) {
var response = client.send(getRequest, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() != 200) {
return new SitemapResult.SitemapError();
}
try (InputStream inputStream = response.body()) {
Document parsedSitemap;
try (InputStream inputStream = response.body()) {
InputStream parserStream;
if (sitemapUrl.path.endsWith(".gz")) {
parserStream = new GZIPInputStream(inputStream);
}
else {
} else {
parserStream = inputStream;
}
Document parsedSitemap = Jsoup.parse(parserStream, "UTF-8", sitemapUrl.toString(), Parser.xmlParser());
parsedSitemap = Jsoup.parse(parserStream, "UTF-8", sitemapUrl.toString(), Parser.xmlParser());
}
finally {
sl.close();
}
if (parsedSitemap.childrenSize() == 0) {
return new SitemapResult.SitemapError();
}
@@ -386,7 +396,7 @@ public class HttpFetcherImpl implements HttpFetcher {
}
private Optional<SimpleRobotRules> fetchAndParseRobotsTxt(EdgeUrl url, WarcRecorder recorder) {
try {
try (var sl = new SendLock()) {
var getRequest = HttpRequest.newBuilder()
.GET()
.uri(url.asURI())
@@ -429,5 +439,30 @@ public class HttpFetcherImpl implements HttpFetcher {
}
}
}
}
class SendLock implements AutoCloseable {
private static final Semaphore maxConcurrentRequests = new Semaphore(Integer.getInteger("crawler.maxConcurrentRequests", 100));
boolean closed = false;
public SendLock() {
maxConcurrentRequests.acquireUninterruptibly();
}
public static <T> HttpResponse<T> wrapSend(HttpClient client, HttpRequest request, HttpResponse.BodyHandler<T> handler) throws IOException, InterruptedException {
try (var lock = new SendLock()) {
return client.send(request, handler);
}
}
@Override
public void close() {
if (!closed) {
maxConcurrentRequests.release();
closed = true;
}
}
}

View File

@@ -82,17 +82,17 @@ public class SearchService extends JoobyService {
jooby.get("/site/https://*", this::handleSiteUrlRedirect);
jooby.get("/site/http://*", this::handleSiteUrlRedirect);
String emptySvg = "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>";
jooby.get("/site/{domain}/favicon", ctx -> {
String domain = ctx.path("domain").value();
logger.info("Finding icon for domain {}", domain);
domainQueries.getDomainId(new EdgeDomain(domain));
try {
DbDomainQueries.DomainIdWithNode domainIdWithNode = domainQueries.getDomainIdWithNode(new EdgeDomain(domain));
var faviconMaybe = faviconClient.getFavicon(domain, domainIdWithNode.nodeAffinity());
if (faviconMaybe.isEmpty()) {
ctx.setResponseCode(404);
return "";
ctx.setResponseType(MediaType.valueOf("image/svg+xml"));
return emptySvg;
} else {
var favicon = faviconMaybe.get();
@@ -102,7 +102,8 @@ public class SearchService extends JoobyService {
}
}
catch (NoSuchElementException ex) {
ctx.setResponseCode(404);
ctx.setResponseType(MediaType.valueOf("image/svg+xml"));
return emptySvg;
}
return "";
});