Skip to content

Commit daf5a20

Browse files
kopporsubhramitpalukku
authored
Refine HTTP Server (#13156)
* Add "demo" library for http - and adapt path of Chocolate.bib * Fix tests * Add little hint on debugging * Update docs/code-howtos/http-server.md Co-authored-by: Subhramit Basu <[email protected]> * Update docs/code-howtos/http-server.md Co-authored-by: Philip <[email protected]> * Update jabsrv/src/test/rest-api.http Co-authored-by: Philip <[email protected]> --------- Co-authored-by: Subhramit Basu <[email protected]> Co-authored-by: Philip <[email protected]>
1 parent e2948d2 commit daf5a20

File tree

7 files changed

+200
-66
lines changed

7 files changed

+200
-66
lines changed

.jbang/JabSrvLauncher.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
3+
//DESCRIPTION jabsrv - serve BibTeX files using JabRef
4+
5+
//JAVA 24
6+
//RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED
7+
8+
//SOURCES ../jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java
9+
//FILES tinylog.properties=../jabsrv-cli/src/main/resources/tinylog.properties
10+
11+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/BibEntryDTO.java
12+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/GlobalExceptionMapper.java
13+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/GsonFactory.java
14+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/JabrefMediaType.java
15+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/JabRefResourceLocator.java
16+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/CORSFilter.java
17+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java
18+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java
19+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/PreferencesFactory.java
20+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/RootResource.java
21+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/Server.java
22+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/services/FilesToServe.java
23+
24+
//REPOS mavencentral,mavencentralsnapshots=https://central.sonatype.com/repository/maven-snapshots/,s01oss=https://s01.oss.sonatype.org/content/repositories/snapshots/,oss=https://oss.sonatype.org/content/repositories,jitpack=https://jitpack.io,oss2=https://oss.sonatype.org/content/groups/public,ossrh=https://oss.sonatype.org/content/repositories/snapshots
25+
26+
//DEPS org.jabref:jablib:6.+
27+
//DEPS info.picocli:picocli:4.7.7
28+
29+
// from jabsrv
30+
//DEPS org.slf4j:slf4j-api:2.0.17
31+
//DEPS org.tinylog:slf4j-tinylog:2.7.0
32+
//DEPS org.tinylog:tinylog-impl:2.7.0
33+
//DEPS org.slf4j:jul-to-slf4j:2.0.17
34+
//DEPS org.apache.logging.log4j:log4j-to-slf4j:2.24.3
35+
//DEPS info.picocli:picocli:4.7.7
36+
//DEPS org.postgresql:postgresql:42.7.5
37+
//DEPS org.bouncycastle:bcprov-jdk18on:1.80
38+
//DEPS com.konghq:unirest-modules-gson:4.4.7
39+
//DEPS jakarta.ws.rs:jakarta.ws.rs-api:4.0.0
40+
//DEPS org.glassfish.jersey.core:jersey-server:3.1.10
41+
//DEPS org.glassfish.jersey.inject:jersey-hk2:3.1.10
42+
//DEPS org.glassfish.hk2:hk2-api:3.1.1
43+
//DEPS org.glassfish.hk2:hk2-utils:3.1.1
44+
//DEPS org.glassfish.hk2:hk2-locator:3.1.1
45+
//DEPS org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.10
46+
//DEPS org.glassfish.grizzly:grizzly-http-server:4.0.2
47+
//DEPS org.glassfish.grizzly:grizzly-framework:4.0.2
48+
//DEPS jakarta.validation:jakarta.validation-api:3.1.1
49+
//DEPS org.hibernate.validator:hibernate-validator:8.0.2.Final
50+
//DEPS com.konghq:unirest-modules-gson:4.4.7
51+
//DEPS com.google.guava:guava:33.4.8-jre
52+
//DEPS org.jabref:afterburner.fx:2.0.0
53+
//DEPS net.harawata:appdirs:1.4.0
54+
//DEPS de.undercouch:citeproc-java:3.3.0
55+
56+
/// This class is required for [jbang](https://www.jbang.dev/)
57+
public class JabSrvLauncher {
58+
public static void main(String[] args) throws Exception {
59+
org.jabref.http.server.cli.ServerCli.main(args);
60+
}
61+
}

docs/code-howtos/http-server.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,35 @@ The resource for a library is implemented at [`org.jabref.http.server.LibraryRes
1010

1111
## Start http server
1212

13+
### Starting with IntelliJ
14+
1315
The class starting the server is located in the project `jabsrv-cli` and is called `org.jabref.http.server.cli.ServerCli`.
1416

1517
Test files to server can be passed as arguments.
16-
If no files are passed, the last opened files are served.
1718
If that list is also empty, the file `src/main/resources/org/jabref/http/server/http-server-demo.bib` is served.
1819

20+
### Starting with JBang
21+
22+
In case you want to interact only with the http server and do not want to set up or run IntelliJ, [JBang](https://www.jbang.dev/download/) can be used.
23+
24+
In the repository root, run following command:
25+
26+
```shell
27+
jbang .jbang/JabSrvLauncher.java
28+
```
29+
30+
JBang also offers running without explicit installation, if you have node installed (and WSL available in the case of Windows):
31+
32+
```shell
33+
npx @jbangdev/jbang .jbang/JabSrvLauncher.java
34+
```
35+
1936
### Starting with gradle
2037

2138
```shell
2239
./gradlew run :jabsrv:run
2340
```
2441

25-
The GUI is also started. Just close it.
26-
2742
Gradle output:
2843

2944
```shell
@@ -50,11 +65,19 @@ INFO: [HttpServer] Started.
5065
DEBUG: Server started.
5166
```
5267

68+
## Served libraries
69+
70+
The last opened libraries are served.
71+
`demo` serves Chocolate.bib.
72+
Additional libraries can be served by passing them as arguments.
73+
5374
## Developing with IntelliJ
5475

55-
IntelliJ Ultimate offers a Markdown-based http-client. One has to open the file `jabsrv/src/test/rest-api.http`.
76+
IntelliJ Ultimate offers a Markdown-based http-client. You need to open the file `jabsrv/src/test/rest-api.http`.
5677
Then, there are play buttons appearing for interacting with the server.
5778

79+
In case you want to debug on Windows, you need to choose "WSL" as the target for the debugger ("Run on") to avoid "command line too long" errors.
80+
5881
## Get SSL Working
5982

6083
When interacting with the [Microsoft Word AddIn](https://github.com/JabRef/JabRef-Word-Addin), a SSL-based connection is required.

jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package org.jabref.http.server.cli;
22

33
import java.net.URI;
4-
import java.net.URISyntaxException;
5-
import java.net.URL;
64
import java.nio.file.Files;
75
import java.nio.file.Path;
86
import java.util.ArrayList;
@@ -24,7 +22,7 @@
2422
public class ServerCli implements Callable<Void> {
2523
private static final Logger LOGGER = LoggerFactory.getLogger(ServerCli.class);
2624

27-
@CommandLine.Parameters(arity = "0..*", paramLabel = "FILE")
25+
@CommandLine.Parameters(arity = "0..*", paramLabel = "FILE", description = "the library files (*.bib) to serve")
2826
List<Path> files;
2927

3028
@CommandLine.Option(names = {"-h", "--host"}, description = "the host name")
@@ -46,11 +44,10 @@ public static void main(final String[] args) throws InterruptedException {
4644

4745
@Override
4846
public Void call() throws InterruptedException {
47+
// The server serves the last opened files (see org.jabref.http.server.LibraryResource.getLibraryPath)
4948
final List<Path> filesToServe = new ArrayList<>(JabRefCliPreferences.getInstance().getLastFilesOpenedPreferences().getLastFilesOpened());
5049

51-
// The server serves the last opened files (see org.jabref.http.server.LibraryResource.getLibraryPath)
52-
// In a testing environment, this might be difficult to handle
53-
// This is a quick solution. The architectural fine solution would use some http context or other @Inject_ed variables in org.jabref.http.server.LibraryResource
50+
// Additionally, files can be provided as args
5451
if (files != null) {
5552
List<Path> filesToAdd = files.stream()
5653
.filter(Files::exists)
@@ -60,32 +57,11 @@ public Void call() throws InterruptedException {
6057
filesToServe.addAll(0, filesToAdd);
6158
}
6259

63-
if (filesToServe.isEmpty()) {
64-
LOGGER.info("No library available to serve, serving the demo library...");
65-
Path bibPath = null;
66-
URL resource = Server.class.getResource("http-server-demo.bib");
67-
if (resource != null) {
68-
try {
69-
bibPath = Path.of(resource.toURI());
70-
} catch (URISyntaxException e) {
71-
LOGGER.error("Error while converting URL to URI", e);
72-
}
73-
}
74-
if (bibPath == null) {
75-
// Server.class.getResource("...") is null when executing with IntelliJ
76-
bibPath = Path.of("src/main/resources/org/jabref/http/server/http-server-demo.bib").toAbsolutePath();
77-
if (Files.notExists(bibPath)) {
78-
bibPath = null;
79-
LOGGER.debug("http-server-demo.bib not found");
80-
}
81-
}
82-
83-
if (bibPath == null) {
84-
LOGGER.info("No library to serve. Please provide a library file as argument.");
85-
} else {
86-
LOGGER.debug("Location of demo library: {}", bibPath);
87-
filesToServe.add(bibPath);
88-
}
60+
// If we are on Windows and checked-out JabRef at the location given in the workspace setup guideline, we can serve Chocolate.bib, too
61+
// Required by rest-api.http
62+
Path exampleChocolateBib = Path.of("C:\\git-repositories\\JabRef\\jablib\\src\\main\\resources\\Chocolate.bib");
63+
if (Files.exists(exampleChocolateBib)) {
64+
filesToServe.add(exampleChocolateBib);
8965
}
9066

9167
LOGGER.debug("Libraries to serve: {}", filesToServe);

jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jabref.http.server;
22

3+
import java.util.ArrayList;
34
import java.util.List;
45

56
import org.jabref.http.server.services.FilesToServe;
@@ -26,6 +27,8 @@ public String get() {
2627
List<String> fileNamesWithUniqueSuffix = filesToServe.getFilesToServe().stream()
2728
.map(p -> p.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(p))
2829
.toList();
29-
return gson.toJson(fileNamesWithUniqueSuffix);
30+
List<String> result = new ArrayList<>(fileNamesWithUniqueSuffix);
31+
result.add("demo");
32+
return gson.toJson(result);
3033
}
3134
}

jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.jabref.http.server;
22

3+
import java.io.BufferedReader;
34
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.io.InputStreamReader;
7+
import java.nio.charset.StandardCharsets;
48
import java.nio.file.Files;
59
import java.util.List;
610
import java.util.Objects;
@@ -13,6 +17,7 @@
1317
import org.jabref.logic.importer.fileformat.BibtexImporter;
1418
import org.jabref.logic.preferences.CliPreferences;
1519
import org.jabref.logic.util.io.BackupFileUtil;
20+
import org.jabref.model.database.BibDatabase;
1621
import org.jabref.model.entry.BibEntryTypesManager;
1722
import org.jabref.model.util.DummyFileUpdateMonitor;
1823

@@ -27,6 +32,8 @@
2732
import jakarta.ws.rs.Produces;
2833
import jakarta.ws.rs.core.MediaType;
2934
import jakarta.ws.rs.core.Response;
35+
import jakarta.ws.rs.core.StreamingOutput;
36+
import org.jspecify.annotations.Nullable;
3037
import org.slf4j.Logger;
3138
import org.slf4j.LoggerFactory;
3239

@@ -45,7 +52,7 @@ public class LibraryResource {
4552

4653
@GET
4754
@Produces(MediaType.APPLICATION_JSON)
48-
public String getJson(@PathParam("id") String id) {
55+
public String getJson(@PathParam("id") String id) throws IOException {
4956
ParserResult parserResult = getParserResult(id);
5057
BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class);
5158
List<BibEntryDTO> list = parserResult.getDatabase().getEntries().stream()
@@ -57,28 +64,29 @@ public String getJson(@PathParam("id") String id) {
5764

5865
@GET
5966
@Produces(JabrefMediaType.JSON_CSL_ITEM)
60-
public String getClsItemJson(@PathParam("id") String id) {
67+
public String getClsItemJson(@PathParam("id") String id) throws IOException {
6168
ParserResult parserResult = getParserResult(id);
6269
JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider();
6370
jabRefItemDataProvider.setData(parserResult.getDatabaseContext(), new BibEntryTypesManager());
6471
return jabRefItemDataProvider.toJson();
6572
}
6673

67-
private ParserResult getParserResult(String id) {
68-
java.nio.file.Path library = getLibraryPath(id);
69-
ParserResult parserResult;
70-
try {
71-
parserResult = new BibtexImporter(preferences.getImportFormatPreferences(), new DummyFileUpdateMonitor()).importDatabase(library);
72-
} catch (IOException e) {
73-
LOGGER.warn("Could not find open library file {}", library, e);
74-
throw new InternalServerErrorException("Could not parse library", e);
75-
}
76-
return parserResult;
77-
}
78-
7974
@GET
8075
@Produces(JabrefMediaType.BIBTEX)
8176
public Response getBibtex(@PathParam("id") String id) {
77+
if ("demo".equals(id)) {
78+
StreamingOutput stream = output -> {
79+
try (InputStream in = getChocolateBibAsStream()) {
80+
in.transferTo(output);
81+
}
82+
};
83+
84+
return Response.ok(stream)
85+
// org.glassfish.jersey.media would be required for a "nice" Java to create ContentDisposition; we avoid this
86+
.header("Content-Disposition", "attachment; filename=\"Chocolate.bib\"")
87+
.build();
88+
}
89+
8290
java.nio.file.Path library = getLibraryPath(id);
8391
String libraryAsString;
8492
try {
@@ -88,6 +96,7 @@ public Response getBibtex(@PathParam("id") String id) {
8896
throw new InternalServerErrorException("Could not read library " + library, e);
8997
}
9098
return Response.ok()
99+
.header("Content-Disposition", "attachment; filename=\"" + library.getFileName() + "\"")
91100
.entity(libraryAsString)
92101
.build();
93102
}
@@ -99,4 +108,30 @@ private java.nio.file.Path getLibraryPath(String id) {
99108
.findAny()
100109
.orElseThrow(NotFoundException::new);
101110
}
111+
112+
private ParserResult getParserResult(String id) throws IOException {
113+
BibtexImporter bibtexImporter = new BibtexImporter(preferences.getImportFormatPreferences(), new DummyFileUpdateMonitor());
114+
115+
if ("demo".equals(id)) {
116+
try (InputStream chocolateBibInputStream = getChocolateBibAsStream()) {
117+
BufferedReader reader = new BufferedReader(new InputStreamReader(chocolateBibInputStream, StandardCharsets.UTF_8));
118+
return bibtexImporter.importDatabase(reader);
119+
}
120+
}
121+
122+
java.nio.file.Path library = getLibraryPath(id);
123+
ParserResult parserResult;
124+
try {
125+
parserResult = bibtexImporter.importDatabase(library);
126+
} catch (IOException e) {
127+
LOGGER.warn("Could not find open library file {}", library, e);
128+
throw new InternalServerErrorException("Could not parse library", e);
129+
}
130+
return parserResult;
131+
}
132+
133+
/// @return a stream to the Chocolate.bib file in the classpath (is null only if the file was moved or there are issues with the classpath)
134+
private @Nullable InputStream getChocolateBibAsStream() {
135+
return BibDatabase.class.getResourceAsStream("/Chocolate.bib");
136+
}
102137
}

jabsrv/src/test/java/org/jabref/http/server/LibrariesResourceTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ protected Application configure() {
2323
void defaultOneTestLibrary() {
2424
String expected = """
2525
[
26+
"%s",
2627
"%s"
27-
]""".formatted(TestBibFile.GENERAL_SERVER_TEST.id);
28+
]""".formatted(TestBibFile.GENERAL_SERVER_TEST.id, "demo");
2829
assertEquals(expected, target("/libraries").request().get(String.class));
2930
}
3031

@@ -35,9 +36,10 @@ void twoTestLibraries() {
3536

3637
String expected = """
3738
[
39+
"%s",
3840
"%s",
3941
"%s"
40-
]""".formatted(TestBibFile.GENERAL_SERVER_TEST.id, TestBibFile.CHOCOLATE_BIB.id);
42+
]""".formatted(TestBibFile.GENERAL_SERVER_TEST.id, TestBibFile.CHOCOLATE_BIB.id, "demo");
4143
assertEquals(expected, target("/libraries").request().get(String.class));
4244
}
4345
}

0 commit comments

Comments
 (0)