mirror of https://github.com/apache/lucene.git
SOLR-8453: test HTTP client error responses
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1723613 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
96360c78c4
commit
fa640e6b69
|
@ -0,0 +1,385 @@
|
|||
package org.apache.solr.client.solrj;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.solr.SolrJettyTestBase;
|
||||
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
|
||||
import org.apache.solr.client.solrj.impl.BinaryRequestWriter;
|
||||
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||
import org.apache.solr.client.solrj.request.RequestWriter;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@SuppressSSL(bugUrl = "https://issues.apache.org/jira/browse/SOLR-5776")
|
||||
public class TestSolrJErrorHandling extends SolrJettyTestBase {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
List<Throwable> unexpected = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeTest() throws Exception {
|
||||
createJetty(legacyExampleCollection1SolrHome());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
unexpected.clear();
|
||||
}
|
||||
|
||||
public String getChain(Throwable th) {
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
Throwable lastCause = null;
|
||||
do {
|
||||
if (lastCause != null) sb.append("->");
|
||||
sb.append(th.getClass().getSimpleName());
|
||||
lastCause = th;
|
||||
th = th.getCause();
|
||||
} while(th != null);
|
||||
sb.append("(" + lastCause.getMessage() + ")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void showExceptions() throws Exception {
|
||||
if (unexpected.isEmpty()) return;
|
||||
|
||||
Map<String,Integer> counts = new HashMap<>();
|
||||
|
||||
// dedup in case there are many clients or many exceptions
|
||||
for (Throwable e : unexpected) {
|
||||
String chain = getChain(e);
|
||||
Integer prev = counts.put(chain, 1);
|
||||
if (prev != null) {
|
||||
counts.put(chain, prev+1);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("EXCEPTION LIST:");
|
||||
for (Map.Entry<String,Integer> entry : counts.entrySet()) {
|
||||
sb.append("\n\t").append(entry.getValue()).append(") ").append(entry.getKey());
|
||||
}
|
||||
|
||||
log.error(sb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithXml() throws Exception {
|
||||
HttpSolrClient client = (HttpSolrClient) getSolrClient();
|
||||
client.setRequestWriter(new RequestWriter());
|
||||
client.deleteByQuery("*:*"); // delete everything!
|
||||
doIt(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBinary() throws Exception {
|
||||
HttpSolrClient client = (HttpSolrClient) getSolrClient();
|
||||
client.setRequestWriter(new BinaryRequestWriter());
|
||||
client.deleteByQuery("*:*"); // delete everything!
|
||||
doIt(client);
|
||||
}
|
||||
|
||||
Iterator<SolrInputDocument> manyDocs(final int base, final int numDocs) {
|
||||
return new Iterator<SolrInputDocument>() {
|
||||
int count = 0;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return count < numDocs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SolrInputDocument next() {
|
||||
int id = base + count++;
|
||||
if (count == 1) { // first doc is legit, and will increment a counter
|
||||
return sdoc("id","test", "count_i", map("inc",1));
|
||||
}
|
||||
// include "ignore_exception" so the log doesn't fill up with known exceptions, and change the values for each doc
|
||||
// so binary format won't compress too much
|
||||
return sdoc("id",Integer.toString(id),"ignore_exception_field_does_not_exist_"+id,"fieldval"+id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void doThreads(final HttpSolrClient client, final int numThreads, final int numRequests) throws Exception {
|
||||
final AtomicInteger tries = new AtomicInteger(0);
|
||||
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
|
||||
for (int i=0; i<numThreads; i++) {
|
||||
final int threadNum = i;
|
||||
threads.add( new Thread() {
|
||||
int reqLeft = numRequests;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (--reqLeft >= 0) {
|
||||
tries.incrementAndGet();
|
||||
doSingle(client, threadNum);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Allow thread to exit, we should have already recorded the exception.
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (Thread thread : threads) {
|
||||
thread.start();
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
showExceptions();
|
||||
|
||||
int count = getCount(client);
|
||||
if (count > tries.get()) {
|
||||
fail("Number of requests was " + tries.get() + " but final count was " + count);
|
||||
}
|
||||
|
||||
assertEquals(tries.get(), getCount(client));
|
||||
|
||||
assertTrue("got unexpected exceptions. ", unexpected.isEmpty() );
|
||||
}
|
||||
|
||||
int getCount(HttpSolrClient client) throws IOException, SolrServerException {
|
||||
client.commit();
|
||||
QueryResponse rsp = client.query(params("q", "id:test", "fl", "count_i", "wt", "json"));
|
||||
int count = ((Number)rsp.getResults().get(0).get("count_i")).intValue();
|
||||
return count;
|
||||
}
|
||||
|
||||
// this always failed with the Jetty 9.3 snapshot
|
||||
void doIt(HttpSolrClient client) throws Exception {
|
||||
client.deleteByQuery("*:*");
|
||||
doThreads(client,10,100);
|
||||
// doSingle(client, 1);
|
||||
}
|
||||
|
||||
void doSingle(HttpSolrClient client, int threadNum) {
|
||||
try {
|
||||
client.add(manyDocs(threadNum*1000000, 1000));
|
||||
}
|
||||
catch (HttpSolrClient.RemoteSolrException e) {
|
||||
String msg = e.getMessage();
|
||||
assertTrue(msg, msg.contains("field_does_not_exist"));
|
||||
}
|
||||
catch (Throwable e) {
|
||||
unexpected.add(e);
|
||||
log.error("unexpected exception:", e);
|
||||
fail("FAILING unexpected exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
@Test
|
||||
public void testLive() throws Exception {
|
||||
HttpSolrClient client = new HttpSolrClient("http://localhost:8983/techproducts/solr/");
|
||||
client.add( sdoc() );
|
||||
doiIt(client);
|
||||
}
|
||||
***/
|
||||
|
||||
String getJsonDocs(int numDocs) {
|
||||
StringBuilder sb = new StringBuilder(numDocs * 20);
|
||||
sb.append("[");
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
sb.append("{ id : '" + i + "' , unknown_field_" + i + " : 'unknown field value' }");
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
char[] whitespace(int n) {
|
||||
char[] arr = new char[n];
|
||||
Arrays.fill(arr, ' ');
|
||||
return arr;
|
||||
}
|
||||
|
||||
String getResponse(InputStream is) throws Exception {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
byte[] buf = new byte[100000];
|
||||
for (;;) {
|
||||
int n = 0;
|
||||
try {
|
||||
n = is.read(buf);
|
||||
} catch (IOException e) {
|
||||
// a real HTTP client probably wouldn't try to read past the end and would thus
|
||||
// not get an exception until the *next* http request.
|
||||
log.error("CAUGHT IOException, but already read " + sb.length() + " : " + getChain(e));
|
||||
}
|
||||
if (n <= 0) break;
|
||||
sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
|
||||
log.info("BUFFER=" + sb.toString());
|
||||
break; // for now, assume we got whole response in one read... otherwise we could block when trying to read again
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpURLConnection() throws Exception {
|
||||
|
||||
String bodyString = getJsonDocs(200000); // sometimes succeeds with this size, but larger can cause OOM from command line
|
||||
|
||||
HttpSolrClient client = (HttpSolrClient) getSolrClient();
|
||||
|
||||
String urlString = client.getBaseURL() + "/update";
|
||||
|
||||
HttpURLConnection conn = null;
|
||||
URL url = new URL(urlString);
|
||||
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
|
||||
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
|
||||
writer.write(bodyString);
|
||||
writer.flush();
|
||||
|
||||
int code = 1;
|
||||
try {
|
||||
code = conn.getResponseCode();
|
||||
} catch (Throwable th) {
|
||||
log.error("ERROR DURING conn.getResponseCode():",th);
|
||||
}
|
||||
|
||||
/***
|
||||
java.io.IOException: Error writing to server
|
||||
at __randomizedtesting.SeedInfo.seed([2928C6EE314CD076:947A81A74F582526]:0)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:665)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:677)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1533)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440)
|
||||
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
|
||||
*/
|
||||
|
||||
log.info("CODE=" + code);
|
||||
InputStream is;
|
||||
if (code == 200) {
|
||||
is = conn.getInputStream();
|
||||
} else {
|
||||
log.info("Attempting to get error stream.");
|
||||
is = conn.getErrorStream();
|
||||
if (is == null) {
|
||||
log.info("Can't get error stream... try input stream?");
|
||||
is = conn.getInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
String rbody = IOUtils.toString(is, StandardCharsets.UTF_8);
|
||||
log.info("RESPONSE BODY:" + rbody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawSocket() throws Exception {
|
||||
|
||||
String hostName = "127.0.0.1";
|
||||
int port = jetty.getLocalPort();
|
||||
|
||||
Socket socket = new Socket(hostName, port);
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
|
||||
// BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
String body = getJsonDocs(100000);
|
||||
int bodyLen = body.length();
|
||||
|
||||
// bodyLen *= 10; // make server wait for more
|
||||
|
||||
char[] whitespace = whitespace(1000000);
|
||||
bodyLen += whitespace.length;
|
||||
|
||||
String headers = "POST /solr/collection1/update HTTP/1.1\n" +
|
||||
"Host: localhost:" + port + "\n" +
|
||||
// "User-Agent: curl/7.43.0\n" +
|
||||
"Accept: */*\n" +
|
||||
"Content-type:application/json\n" +
|
||||
"Content-Length: " + bodyLen + "\n" +
|
||||
"Connection: Keep-Alive\n";
|
||||
|
||||
out.write(headers);
|
||||
out.write("\n"); // extra newline separates headers from body
|
||||
out.write(body);
|
||||
out.flush();
|
||||
|
||||
// Now what if I try to write more? This doesn't seem to throw an exception!
|
||||
Thread.sleep(1000);
|
||||
out.write(whitespace); // whitespace
|
||||
out.flush();
|
||||
|
||||
String rbody = getResponse(in); // This will throw a connection reset exception if you try to read past the end of the HTTP response
|
||||
log.info("RESPONSE BODY:" + rbody);
|
||||
assertTrue(rbody.contains("unknown_field"));
|
||||
|
||||
/***
|
||||
// can I reuse now?
|
||||
// writing another request doesn't actually throw an exception, but the following read does
|
||||
out.write(headers);
|
||||
out.write("\n"); // extra newline separates headers from body
|
||||
out.write(body);
|
||||
out.flush();
|
||||
|
||||
rbody = getResponse(in);
|
||||
log.info("RESPONSE BODY:" + rbody);
|
||||
assertTrue(rbody.contains("unknown_field"));
|
||||
***/
|
||||
|
||||
|
||||
IOUtils.closeQuietly(out);
|
||||
IOUtils.closeQuietly(in);
|
||||
IOUtils.closeQuietly(socket);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue