Implemented searching option using a network endpoint.

Change-Id: I1a35d482f0df49fc1acaef9aca027eb2fa551401
diff --git a/core/Changes b/core/Changes
index 87d954c..1926b51 100644
--- a/core/Changes
+++ b/core/Changes
@@ -2,7 +2,7 @@
 
  - Added OAuth2 scopes: INSTALL_USER_CLIENT, UNINSTALL_USER_CLIENT
  - Added status codes
- 
+ - Implemented searching option using a network endpoint
 
 # version 0.67.1
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
index 95c7843..b020c9e 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
@@ -92,6 +92,7 @@
 
     // deprec?!
     private final BACKENDS DEFAULT_ENGINE = BACKENDS.LUCENE;
+    private String networkEndpointURL;
     
     // license patterns
     protected Pattern publicLicensePattern;
@@ -203,6 +204,10 @@
                 properties.getProperty("security.tokenTTL", "72H"));
         shortTokenTTL = TimeUtils.convertTimeToSeconds(
                 properties.getProperty("security.shortTokenTTL", "3H"));
+        
+        // network endpoint
+        networkEndpointURL =
+                properties.getProperty("network.endpoint.url", "");
     }
     
     @Deprecated
@@ -265,7 +270,7 @@
     }
 
     public enum BACKENDS {
-        NEO4J, LUCENE
+        NEO4J, LUCENE, NETWORK
     }
 
 }
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 33d85d7..d10b973 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -30,6 +30,8 @@
     public static final int NO_RESOURCE_FOUND = 114;
     public static final int DEPRECATED_PARAMETER = 115;
     public static final int CACHING_VC = 116;
+    public static final int NETWORK_ENDPOINT_NOT_AVAILABLE = 117;
+    public static final int SEARCH_NETWORK_ENDPOINT_FAILED = 118;
     
     /**
      * 200 status codes general JSON serialization error
diff --git a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
index 780ff22..6a7d90e 100644
--- a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
+++ b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
@@ -40,6 +40,7 @@
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.ClientsHandler;
 import de.ids_mannheim.korap.web.SearchKrill;
+import de.ids_mannheim.korap.web.SearchNetworkEndpoint;
 
 @Service
 public class SearchService extends BasicService{
@@ -50,8 +51,6 @@
 
     @Autowired
     private KustvaktConfiguration config;
-//    @Autowired
-//    private VCLoader vcLoader;
     @Autowired
     private AuthenticationManager authenticationManager;
 
@@ -60,6 +59,8 @@
 
     @Autowired
     private SearchKrill searchKrill;
+    @Autowired
+    private SearchNetworkEndpoint searchNetwork;
 
     private ClientsHandler graphDBhandler;
 
@@ -148,7 +149,6 @@
             pipeArray = pipes.split(",");
         }
         
-        KustvaktConfiguration.BACKENDS eng = this.config.chooseBackend(engine);
         User user = createUser(username, headers);
         CorpusAccess corpusAccess = user.getCorpusAccess();
         
@@ -195,10 +195,14 @@
             jlog.debug("the serialized query " + query);
         }
 
+        KustvaktConfiguration.BACKENDS searchEngine = this.config.chooseBackend(engine);
         String result;
-        if (eng.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
+        if (searchEngine.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
             result = searchNeo4J(query, pageLength, meta, false);
         }
+        else if (searchEngine.equals(KustvaktConfiguration.BACKENDS.NETWORK)) {
+            result = searchNetwork.search(query);
+        }
         else {
             result = searchKrill.search(query);
         }
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/SearchNetworkEndpoint.java b/core/src/main/java/de/ids_mannheim/korap/web/SearchNetworkEndpoint.java
new file mode 100644
index 0000000..1f2063a
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/web/SearchNetworkEndpoint.java
@@ -0,0 +1,87 @@
+package de.ids_mannheim.korap.web;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.apache.http.HttpStatus;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+
+@Service
+public class SearchNetworkEndpoint {
+
+    private final static Logger jlog = LogManager
+            .getLogger(SearchNetworkEndpoint.class);
+
+    @Autowired
+    private KustvaktConfiguration config;
+
+    public String search (String query) throws KustvaktException {
+        String networkEndpointURL = config.getNetworkEndpointURL();
+        if (networkEndpointURL == null || networkEndpointURL.isEmpty()) {
+            throw new KustvaktException(
+                    StatusCodes.NETWORK_ENDPOINT_NOT_AVAILABLE,
+                    "Network endpoint is not available");
+        }
+        else {
+            try {
+                URL url = new URL(networkEndpointURL);
+                HttpURLConnection connection = (HttpURLConnection) url
+                        .openConnection();
+                connection.setRequestMethod("POST");
+                connection.setRequestProperty("Content-Type",
+                        "application/json; charset=UTF-8");
+                connection.setRequestProperty("Accept", "application/json");
+                connection.setDoOutput(true);
+                OutputStream os = connection.getOutputStream();
+                byte[] input = query.getBytes("utf-8");
+                os.write(input, 0, input.length);
+
+                String entity = null;
+                if (connection.getResponseCode() == HttpStatus.SC_OK) {
+                    BufferedReader br = new BufferedReader(
+                            new InputStreamReader(connection.getInputStream(),
+                                    "utf-8"));
+                    StringBuilder response = new StringBuilder();
+                    String responseLine = null;
+                    while ((responseLine = br.readLine()) != null) {
+                        response.append(responseLine.trim());
+                    }
+                    entity = response.toString();
+                }
+
+                if (entity != null && !entity.isEmpty()) {
+                    return entity;
+                }
+                else {
+                    String message = connection.getResponseCode() + " "
+                            + connection.getResponseMessage();
+                    jlog.warn("Search on network endpoint failed "
+                            + networkEndpointURL + ". Message: " + message);
+
+                    throw new KustvaktException(
+                            StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                            "Failed searching at network endpoint: "
+                                    + networkEndpointURL,
+                            message);
+                }
+            }
+            catch (Exception e) {
+                throw new KustvaktException(
+                        StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                        "Failed searching at network endpoint: "
+                                + networkEndpointURL,
+                        e.getCause());
+            }
+        }
+    }
+}
diff --git a/core/src/test/resources/network-output/search-result.jsonld b/core/src/test/resources/network-output/search-result.jsonld
new file mode 100644
index 0000000..da55e09
--- /dev/null
+++ b/core/src/test/resources/network-output/search-result.jsonld
@@ -0,0 +1,130 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+    "meta": {
+        "count": 2,
+        "startIndex": 0,
+        "timeout": 10000,
+        "context": {
+            "left": [
+                "token",
+                6
+            ],
+            "right": [
+                "token",
+                6
+            ]
+        },
+        "fields": [
+            "ID",
+            "UID",
+            "textSigle",
+            "corpusID",
+            "author",
+            "title",
+            "subTitle",
+            "textClass",
+            "pubPlace",
+            "pubDate",
+            "availability",
+            "layerInfos",
+            "docSigle",
+            "corpusSigle"
+        ],
+        "version": "0.60.4",
+        "benchmark": "1.503497374 s",
+        "totalResults": 67249248,
+        "serialQuery": "tokens:s:der",
+        "itemsPerPage": 2
+    },
+    "query": {
+        "@type": "koral:token",
+        "wrap": {
+            "@type": "koral:term",
+            "match": "match:eq",
+            "layer": "orth",
+            "key": "der",
+            "foundry": "opennlp",
+            "rewrites": [
+                {
+                    "@type": "koral:rewrite",
+                    "src": "Kustvakt",
+                    "operation": "operation:injection",
+                    "scope": "foundry"
+                }
+            ]
+        }
+    },
+    "collection": {
+        "@type": "koral:doc",
+        "match": "match:eq",
+        "type": "type:regex",
+        "value": "CC-BY.*",
+        "key": "availability",
+        "rewrites": [
+            {
+                "@type": "koral:rewrite",
+                "src": "Kustvakt",
+                "operation": "operation:insertion",
+                "scope": "availability(FREE)"
+            }
+        ]
+    },
+    "matches": [
+        {
+            "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+            "meta": {},
+            "hasSnippet": true,
+            "hasTokens": false,
+            "matchID": "match-WUD17/B96/48580-p40-41",
+            "textClass": "staat-gesellschaft biographien-interviews fiktion vermischtes",
+            "textSigle": "WUD17/B96/48580",
+            "author": "Andy king50, u.a.",
+            "docSigle": "WUD17/B96",
+            "layerInfos": "corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels marmot/m=tokens marmot/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens",
+            "pubPlace": "URL:http://de.wikipedia.org",
+            "availability": "CC-BY-SA",
+            "title": "Benutzer Diskussion:Brettspieler",
+            "pubDate": "2017-07-01",
+            "corpusSigle": "WUD17",
+            "context": {
+                "left": [
+                    "token",
+                    6
+                ],
+                "right": [
+                    "token",
+                    6
+                ]
+            },
+            "snippet": "<span class=\"context-left\"><span class=\"more\"><\/span>konstruktiv mitarbeiten kannst, erfährst du auf <\/span><span class=\"match\"><mark>der<\/mark><\/span><span class=\"context-right\"> Seite  Starthilfe . Grüße,  19:05, 6. Nov.<span class=\"more\"><\/span><\/span>"
+        },
+        {
+            "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+            "meta": {},
+            "hasSnippet": true,
+            "hasTokens": false,
+            "matchID": "match-WUD17/B96/50115-p40-41",
+            "textClass": "staat-gesellschaft biographien-interviews fiktion vermischtes",
+            "textSigle": "WUD17/B96/50115",
+            "author": "Doc.Heintz, u.a.",
+            "docSigle": "WUD17/B96",
+            "layerInfos": "corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels marmot/m=tokens marmot/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens",
+            "pubPlace": "URL:http://de.wikipedia.org",
+            "availability": "CC-BY-SA",
+            "title": "Benutzer Diskussion:Blkviz",
+            "pubDate": "2017-07-01",
+            "corpusSigle": "WUD17",
+            "context": {
+                "left": [
+                    "token",
+                    6
+                ],
+                "right": [
+                    "token",
+                    6
+                ]
+            },
+            "snippet": "<span class=\"context-left\"><span class=\"more\"><\/span>konstruktiv mitarbeiten kannst, erfährst du auf <\/span><span class=\"match\"><mark>der<\/mark><\/span><span class=\"context-right\"> Seite  Starthilfe . Grüße,  20:11, 7.<span class=\"more\"><\/span><\/span>"
+        }
+    ]
+}
diff --git a/full/Changes b/full/Changes
index b179a17..049a39f 100644
--- a/full/Changes
+++ b/full/Changes
@@ -17,7 +17,9 @@
  - Added new APIs: list user-installed plugins and uninstall plugin.
  - Moved install and list plugin APIs to PluginController and updated their
    service paths under /plugins.
- 
+2022-06-03 
+ - Implemented searching option using a network endpoint
+
  
 # version 0.67.1
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java
new file mode 100644
index 0000000..27e6695
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java
@@ -0,0 +1,123 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchNetworkEndpointTest extends SpringJerseyTest {
+
+    @Autowired
+    private KustvaktConfiguration config;
+
+    private ClientAndServer mockServer;
+    private MockServerClient mockClient;
+
+    private int port = 6081;
+    private String searchResult;
+    private String endpointURL = "http://localhost:"+port+"/searchEndpoint";
+
+    public SearchNetworkEndpointTest () throws IOException {
+        searchResult = IOUtils.toString(
+                ClassLoader.getSystemResourceAsStream(
+                        "network-output/search-result.jsonld"),
+                StandardCharsets.UTF_8);
+    }
+
+
+    @Before
+    public void startMockServer () {
+        mockServer = startClientAndServer(port);
+        mockClient = new MockServerClient("localhost", mockServer.getPort());
+    }
+
+
+    @After
+    public void stopMockServer () {
+        mockServer.stop();
+    }
+
+
+    @Test
+    public void testSearchNetwork ()
+            throws IOException, KustvaktException, URISyntaxException {
+        config.setNetworkEndpointURL(endpointURL);
+        mockClient.reset()
+                .when(request().withMethod("POST").withPath("/searchEndpoint")
+                        .withHeaders(
+                                new Header("Content-Type",
+                                        "application/json; charset=utf-8"),
+                                new Header("Accept", "application/json")))
+                .respond(response()
+                        .withHeader(new Header("Content-Type",
+                                "application/json; charset=utf-8"))
+                        .withBody(searchResult).withStatusCode(200));
+
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+
+        assertEquals(2, node.at("/matches").size());
+    }
+
+
+    @Test
+    public void testSearchWithUnknownURL ()
+            throws IOException, KustvaktException {
+        config.setNetworkEndpointURL("http://localhost:1040/search");
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+        
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(),
+                response.getStatus());
+    }
+    
+    @Test
+    public void testSearchWithUnknownHost () throws KustvaktException {
+        config.setNetworkEndpointURL("http://search.com");
+        
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(),
+                response.getStatus());
+    }
+}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java
index 99a6d86..2d95b19 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java
@@ -37,8 +37,9 @@
     private ClientAndServer mockServer;
     private MockServerClient mockClient;
 
+    private int port = 6071;
     private String pipeJson, pipeWithParamJson;
-    private String glemmUri = "http://localhost:1080/glemm";
+    private String glemmUri = "http://localhost:"+port+"/glemm";
 
     public SearchPipeTest () throws URISyntaxException, IOException {
         pipeJson = IOUtils.toString(
@@ -54,7 +55,7 @@
 
     @Before
     public void startMockServer () {
-        mockServer = startClientAndServer(1080);
+        mockServer = startClientAndServer(port);
         mockClient = new MockServerClient("localhost", mockServer.getPort());
     }
 
@@ -74,7 +75,7 @@
                                 "application/json; charset=utf-8"))
                         .withBody("{test}").withStatusCode(200));
 
-        URL url = new URL("http://localhost:1080/test");
+        URL url = new URL("http://localhost:"+port+"/test");
         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
         connection.setRequestMethod("POST");
         connection.setRequestProperty("Content-Type",
@@ -233,7 +234,7 @@
                 .when(request().withMethod("POST").withPath("/non-json-pipe"))
                 .respond(response().withStatusCode(415));
 
-        String pipeUri = "http://localhost:1080/non-json-pipe";
+        String pipeUri = "http://localhost:"+port+"/non-json-pipe";
 
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
@@ -287,7 +288,7 @@
                         .withHeaders(new Header("Content-Type",
                                 "application/json; charset=utf-8")));
 
-        String pipeUri = "http://localhost:1080/invalid-response";
+        String pipeUri = "http://localhost:"+port+"/invalid-response";
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
                 .queryParam("pipes", pipeUri).get(ClientResponse.class);
@@ -311,7 +312,7 @@
                                 new Header("Accept", "application/json")))
                 .respond(response().withBody("blah").withStatusCode(200));
 
-        String pipeUri = "http://localhost:1080/plain-text";
+        String pipeUri = "http://localhost:"+port+"/plain-text";
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
                 .queryParam("pipes", pipeUri).get(ClientResponse.class);
diff --git a/lite/Changes b/lite/Changes
index d9554fa..9460dd9 100644
--- a/lite/Changes
+++ b/lite/Changes
@@ -1,5 +1,9 @@
 # version 0.68
 
+2022-06-03 
+ - Implemented searching option using a network endpoint
+
+
 # version 0.67
 
 # version 0.66
diff --git a/lite/src/main/resources/lite-config.xml b/lite/src/main/resources/lite-config.xml
index 3e7aacd..a914f95 100644
--- a/lite/src/main/resources/lite-config.xml
+++ b/lite/src/main/resources/lite-config.xml
@@ -137,10 +137,12 @@
 	<bean id="annotationParser" class="de.ids_mannheim.korap.annotation.AnnotationParser"
 		scope="singleton" />
 
-	<!-- Krill -->
+	<!-- Search Engine -->
 	<bean id="search_krill" class="de.ids_mannheim.korap.web.SearchKrill">
 		<constructor-arg value="${krill.indexDir}" />
 	</bean>
+	<bean id="search_network" class="de.ids_mannheim.korap.web.SearchNetworkEndpoint"/>
+	
 
 	<!-- Filters -->
 	<!-- <bean id="APIVersionFilter" class="de.ids_mannheim.korap.web.APIVersionFilter"
diff --git a/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchPipeTest.java b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchPipeTest.java
index 38e5f21..ac69a61 100644
--- a/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchPipeTest.java
+++ b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchPipeTest.java
@@ -37,8 +37,9 @@
     private ClientAndServer mockServer;
     private MockServerClient mockClient;
 
+    private int port = 6070;
     private String pipeJson, pipeWithParamJson;
-    private String glemmUri = "http://localhost:1080/glemm";
+    private String glemmUri = "http://localhost:"+port+"/glemm";
 
     public LiteSearchPipeTest () throws IOException{
         pipeJson = IOUtils.toString(
@@ -54,7 +55,7 @@
 
     @Before
     public void startMockServer () {
-        mockServer = startClientAndServer(1080);
+        mockServer = startClientAndServer(port);
         mockClient = new MockServerClient("localhost", mockServer.getPort());
     }
 
@@ -74,7 +75,7 @@
                                 "application/json; charset=utf-8"))
                         .withBody("{test}").withStatusCode(200));
 
-        URL url = new URL("http://localhost:1080/test");
+        URL url = new URL("http://localhost:"+port+"/test");
         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
         connection.setRequestMethod("POST");
         connection.setRequestProperty("Content-Type",
@@ -226,7 +227,7 @@
                 .when(request().withMethod("POST").withPath("/non-json-pipe"))
                 .respond(response().withStatusCode(415));
 
-        String pipeUri = "http://localhost:1080/non-json-pipe";
+        String pipeUri = "http://localhost:"+port+"/non-json-pipe";
 
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
@@ -280,7 +281,7 @@
                         .withHeaders(new Header("Content-Type",
                                 "application/json; charset=utf-8")));
 
-        String pipeUri = "http://localhost:1080/invalid-response";
+        String pipeUri = "http://localhost:"+port+"/invalid-response";
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
                 .queryParam("pipes", pipeUri).get(ClientResponse.class);
@@ -304,7 +305,7 @@
                                 new Header("Accept", "application/json")))
                 .respond(response().withBody("blah").withStatusCode(200));
 
-        String pipeUri = "http://localhost:1080/plain-text";
+        String pipeUri = "http://localhost:"+port+"/plain-text";
         ClientResponse response = resource().path(API_VERSION).path("search")
                 .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
                 .queryParam("pipes", pipeUri).get(ClientResponse.class);
diff --git a/lite/src/test/java/de/ids_mannheim/korap/web/service/SearchNetworkEndpointTest.java b/lite/src/test/java/de/ids_mannheim/korap/web/service/SearchNetworkEndpointTest.java
new file mode 100644
index 0000000..c00084d
--- /dev/null
+++ b/lite/src/test/java/de/ids_mannheim/korap/web/service/SearchNetworkEndpointTest.java
@@ -0,0 +1,123 @@
+package de.ids_mannheim.korap.web.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchNetworkEndpointTest extends LiteJerseyTest {
+
+    @Autowired
+    private KustvaktConfiguration config;
+
+    private ClientAndServer mockServer;
+    private MockServerClient mockClient;
+
+    private int port = 6080;
+    private String searchResult;
+    private String endpointURL = "http://localhost:"+port+"/searchEndpoint";
+
+    public SearchNetworkEndpointTest () throws IOException {
+        searchResult = IOUtils.toString(
+                ClassLoader.getSystemResourceAsStream(
+                        "network-output/search-result.jsonld"),
+                StandardCharsets.UTF_8);
+    }
+
+
+    @Before
+    public void startMockServer () {
+        mockServer = startClientAndServer(port);
+        mockClient = new MockServerClient("localhost", mockServer.getPort());
+    }
+
+
+    @After
+    public void stopMockServer () {
+        mockServer.stop();
+    }
+
+
+    @Test
+    public void testSearchNetwork ()
+            throws IOException, KustvaktException, URISyntaxException {
+        config.setNetworkEndpointURL(endpointURL);
+        mockClient.reset()
+                .when(request().withMethod("POST").withPath("/searchEndpoint")
+                        .withHeaders(
+                                new Header("Content-Type",
+                                        "application/json; charset=utf-8"),
+                                new Header("Accept", "application/json")))
+                .respond(response()
+                        .withHeader(new Header("Content-Type",
+                                "application/json; charset=utf-8"))
+                        .withBody(searchResult).withStatusCode(200));
+
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+
+        assertEquals(2, node.at("/matches").size());
+    }
+
+
+    @Test
+    public void testSearchWithUnknownURL ()
+            throws IOException, KustvaktException {
+        config.setNetworkEndpointURL("http://localhost:1040/search");
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+        
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(),
+                response.getStatus());
+    }
+    
+    @Test
+    public void testSearchWithUnknownHost () throws KustvaktException {
+        config.setNetworkEndpointURL("http://search.com");
+        
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+                .queryParam("engine", "network").get(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(),
+                response.getStatus());
+    }
+}
diff --git a/lite/src/test/resources/test-config.xml b/lite/src/test/resources/test-config.xml
index 6eec95f..b3f508e 100644
--- a/lite/src/test/resources/test-config.xml
+++ b/lite/src/test/resources/test-config.xml
@@ -128,10 +128,12 @@
 		<property name="dataSource" ref="sqliteDataSource" />
 	</bean>
 
-	<!-- Krill -->
+	<!-- Search Engine -->
 	<bean id="search_krill" class="de.ids_mannheim.korap.web.SearchKrill">
 		<constructor-arg value="${krill.indexDir}" />
 	</bean>
+	<bean id="search_network" class="de.ids_mannheim.korap.web.SearchNetworkEndpoint"/>
+	
 
 	<!-- Filters -->
 	<!-- <bean id="APIVersionFilter" class="de.ids_mannheim.korap.web.APIVersionFilter"