Taogen's Blog

Stay hungry stay foolish.

When you send an HTTP request with a different domain than your page’s domain (or IP address + port number), a CORS error may occur. A CORS error means that the API server rejected your request. To access other domain API from your web page, the backend server you requested must set some CORS headers in the HTTP response to allow CORS requests. Below are some errors caused by incorrectly set HTTP response headers for CORS requests.

Error: No ‘Access-Control-Allow-Origin’ header is present

Error information in web browser console

Access to XMLHttpRequest at 'http://localhost:8081/api' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
GET http://localhost:8081/api net::ERR_FAILED 302

Solutions

First, check that the URL, Method, and Content-Type you requested are correct.

Make sure the server API is up and running.

Enable CORS requests for your server API. Add Access-Control-Allow-Origin in HTTP response header.

Access-Control-Allow-Origin: *
or
Access-Control-Allow-Origin: http://your_page_domain

For example, in Java web projects.

response.setHeader("Access-Control-Allow-Origin", "*");
// or
response.setHeader("Access-Control-Allow-Origin", "http://localhost");

Reasons

The API is not shared with other origins. You need to update the API CORS policy by set Access-Control-Allow-Origin in response headers.

Error: Method xxx is not allowed

Error information in web browser console

Access to XMLHttpRequest at 'http://localhost:8081/api/delete' from origin 'http://localhost' has been blocked by CORS policy: Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.

Solutions

Add Access-Control-Allow-Methods: {method_name_in_error_message} in HTTP response header. Note that method names must be capitalized.

For example, in Java web projects.

response.setHeader("Access-Control-Allow-Methods", "DELETE");

Reasons

The default allowed HTTP methods for CORS are GET, POST, and HEAD. For other HTTP methods like DELETE or PUT, you need to add it to HTTP response header Access-Control-Allow-Methods.

Error: Request header field xxx is not allowed

Error information in web browser console

Access to XMLHttpRequest at 'http://localhost:8081/api/delete' from origin 'http://localhost' has been blocked by CORS policy: Request header field my-header1 is not allowed by Access-Control-Allow-Headers in preflight response.
Access to XMLHttpRequest at 'http://localhost:8081/api/get?name=Jack' from origin 'http://localhost' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

Solutions

Add Access-Control-Allow-Headers: {header_field_name_in_error_message} in HTTP response header.

For example, in Java web projects.

response.setHeader("Access-Control-Allow-Headers", "my-header1");

Reasons

The default allowed HTTP headers for CORS requests are:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (value only be application/x-www-form-urlencoded, multipart/form-data, or text/plain)
  • Range

For other HTTP headers, you need to add them to HTTP response header Access-Control-Allow-Headers.

Error: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’

Error information in web browser console

Access to XMLHttpRequest at 'http://localhost:8081/api/get' from origin 'http://localhost' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Solutions

Set the value of Access-Control-Allow-Origin to your page domain instead of * in HTTP response header. And set the value of Access-Control-Allow-Credentials to true.

For example, in Java web projects.

response.setHeader("Access-Control-Allow-Origin", "http://localhost");
response.setHeader("Access-Control-Allow-Credentials", "true");

Reasons

When you send a CORS request with credentials, you must set a specific domain in Access-Control-Allow-Origin.

Request with credentials: withCredentials: true

const xhr = new XMLHttpRequest();
const url = 'http://localhost:8081/api/get';
xhr.open('GET', url, true);
xhr.withCredentials = true;
xhr.onload = function () {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200) {
console.log(xhr.response);
}
}
};
xhr.send();
$.ajax({
url: "http://localhost:8081/api/get",
method: "GET",
xhrFields: {
withCredentials: true
},
}).done(function(res) {
console.log(res);
});

Error: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ‘’ which must be ‘true’

Error information in web browser console

Access to XMLHttpRequest at 'http://localhost:8081/api/get' from origin 'http://localhost' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Solutions

Add Access-Control-Allow-Credentials: true in HTTP response header.

For example, in Java web projects.

response.setHeader("Access-Control-Allow-Credentials", "true");

Reasons

When the request’s credentials flag is true, the HTTP response header Access-Control-Allow-Credentials should be true.

Conclusion

There are two scenarios for setting CORS headers. The headers you need to set in each case are given below.

1. No credentials

response.setHeader("Access-Control-Allow-Origin", "*"); // You can also set a specific host.
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT, PATCH");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, {my-custome-header}");
response.setHeader("Access-Control-Max-Age", "86400");

2. With credentials

response.setHeader("Access-Control-Allow-Origin", "{your_host}"); // If you use a web framework, it may support setting allow-origin patterns. For example, http://localhost:[*], http://192.168.0.*:[*].
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT, PATCH");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, {my-custome-header}");
response.setHeader("Access-Control-Max-Age", "86400");

Background

When the frontend calls the backend API, the response status is 200, and my web browser dev tool shows “failed to load response data.”

HTTP response details in the web browser dev tool

  • Status Code: 200
  • Content-Type: application/json;charset=UTF-8
  • Response Body: Failed to load response data: No data found for resource with given identifier

I copy the URL of the requested API to a new tab of my web browser (get a request from a browser), but I only get part of the JSON data.

{
part of JSON

Error Info

Failed to load response data: No data found for resource with given identifier

Solutions

Make sure your server disk has free space.

Reasons

Because my server’s disk is full, the HTTP response returns part of JSON.

References

Multiple attempts to get the result

public class LogicProcessingUtils {
/**
* @param maxTryTimes
* @param successPredicate
* @param function
* @param params parameter object
* @param <T> function return type
* @param <P> function parameter type
* @return
*/
public static <T, P> T tryToGetResult(int maxTryTimes,
Predicate<T> successPredicate,
Function<P, T> function,
P param) {
int tryTimes = 0;
T t = null;
while (tryTimes < maxTryTimes) {
t = function.apply(param);
// System.out.println(t);
if (successPredicate.test(t)) {
break;
}
tryTimes++;
}
return t;
}

public static <T> T tryToGetResult(int maxTryTimes,
Predicate<T> successPredicate,
Supplier<T> supplier) {
int tryTimes = 0;
T t = null;
while (tryTimes < maxTryTimes) {
t = supplier.get();
// System.out.println(t);
if (successPredicate.test(t)) {
break;
}
tryTimes++;
}
return t;
}
}
public class MyTest {
public static void main(String[] args) {
MyTest myTest = new MyTest();
int maxTryTimes = 3;
ObjectNode result = LogicProcessingUtils.tryToGetResult(
maxTryTimes,
jsonNode -> jsonNode.get("code").asInt() == 200,
myTest::getResult,
0);
System.out.println(result);
}

public ObjectNode getResult(Object param) {
System.out.println("param: " + param);
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(10);
System.out.println("random number: " + randomNumber);
String jsonStr = "{\"code\":500,\"data\":%d,\"msg\":\"请求失败\"}";
ObjectMapper objectMapper = new ObjectMapper();
try {
return (ObjectNode) objectMapper.readTree(String.format(jsonStr, randomNumber));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}

Get Task Results Concurrently

ExecutorService

the order to get future results is the tasks order. If the later task executes faster than the earlier task, you still need to wait for the earlier task to complete.

public static void executorService() {
int threadNum = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
long start = System.currentTimeMillis();
List<Future> futureList = new ArrayList<>(threadNum);
for (int i = 0; i < threadNum; i++) {
Future<String> future = executorService.submit(() -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + ": " + randomNumber);
Thread.sleep(1000 * randomNumber);
return Thread.currentThread().getName();
});
futureList.add(future);
}
for (int i = 0; i < threadNum; i++) {
try {
System.out.println(futureList.get(i).get());
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
executorService.shutdown();
}

ExecutorCompletionService

the order to get future results is the execution time of tasks from short to long.

public static void executorCompletionService() {
int threadNum = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
ExecutorCompletionService completionService =
new ExecutorCompletionService<>(executorService);
long start = System.currentTimeMillis();
for (int i = 0; i < threadNum; i++) {
completionService.submit(() -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + ": " + randomNumber);
Thread.sleep(1000 * randomNumber);
return Thread.currentThread().getName();
});
}
for (int i = 0; i < threadNum; i++) {
try {
System.out.println(completionService.take().get());
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
executorService.shutdown();
}

Convert String to Date Time from Request Query String, payload of x-www-urlencoded or form-data

@GetMapping("testDateTime1")
public String testDateTime(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date dateTime) {
...
}
@RequestMapping("testDateTime2")
public String testDateTime2(QueryVo queryVo) {
...
}
public class QueryVo {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date dateTime;
}

Convert String to Date Time from Request payload of application/json

@PostMapping("testDateTime3")
public String testDateTime3(@RequestBody QueryVo queryVo) {
System.out.println("testDateTime");
return queryVo.toString();
}

public class QueryVo {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date dateTime;
}

Random Values

Random Integer

new Random().nextInt(min, max+1) return [min, max]

// Java 8
int size = 1, min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.ints(size, min, max + 1).findFirst().getAsInt();

int size = 5, min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int[] randomNumbers = random.ints(size, min, max + 1).toArray();

int size = 5, min = 0, max = 10;
Random random = new Random();
int[] randomNumbers = random.ints(size, min, max + 1).toArray();
// Java 1.7 or later
int min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNum = random.nextInt(min, max + 1);
// Before Java 1.7
int min = 0, max = 10;
Random random = new Random();
int randomNum = random.nextInt((max - min) + 1) + min;

Math.random() return [0, 1)

Random.nextInt(n) is more efficient than Math.random() * n

Math.random() uses Random.nextDouble() internally. Random.nextDouble() uses Random.next() twice to generate a double that has approximately uniformly distributed bits in its mantissa, so it is uniformly distributed in the range 0 to 1-(2^-53).

Random.nextInt(n) uses Random.next() less than twice on average- it uses it once, and if the value obtained is above the highest multiple of n below MAX_INT it tries again, otherwise is returns the value modulo n (this prevents the values above the highest multiple of n below MAX_INT skewing the distribution), so returning a value which is uniformly distributed in the range 0 to n-1.

int randomNum = min + (int) (Math.random() * ((max - min) + 1));

Random float

// TODO

Random String

// TODO

Regex Expression

matches() - Checks if the regexp matches the complete string.

String source = "hello there, I am a Java developer. She is a Web developer.";
Pattern pattern = Pattern.compile("a (.*?) developer");
Matcher matcher = pattern.matcher(source);
System.out.println(matcher.matches()); // false

Pattern pattern2 = Pattern.compile(".*a (.*?) developer.*");
Matcher matcher2 = pattern2.matcher(source);
System.out.println(matcher2.matches()); // true

find() - Get matching substrings

String source = "hello there, I am a Java developer. She is a Web developer.";
Pattern pattern = Pattern.compile("a (.*?) developer", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(source);
// find all match strings
while (matcher.find()) {
System.out.println(matcher.group()); // a Java developer, a Web developer
for (int i = 1; i < matcher.groupCount(); i++) {
System.out.println(matcher.group(i)); // Java, Web
}
}
// reset matcher for reuse
matcher.reset();
// only find once
if (matcher.find()) {
System.out.println(matcher.group()); // a Java developer
System.out.println(matcher.group(1)); // Java
}

Replace group string

String source = "hello there, I am a Java developer. She is a Web developer.";
String regex = "a (.*?) developer";
int groupToReplace = 1;
String replacement = "Good";
Matcher matcher = Pattern.compile(regex).matcher(source);
StringBuilder result = new StringBuilder(source);
int adjust = 0;
while (matcher.find()) {
int start = matcher.start(groupToReplace);
int end = matcher.end(groupToReplace);
result.replace(start + adjust, end + adjust, replacement);
adjust += replacement.length() - (end - start);
}
System.out.println(result); //hello there, I am a Good developer. She is a Good developer.

Non-greedy regular expression

Followed by the ?. For example .*?

Match special characters

[\\special_character]

For example:

[\\(\\)\\[\\]]

Match multiple line string

// Pattern.DOTALL
Pattern.compile("regexStr", Pattern.DOTALL);

Match case-insensitive string

// Pattern.CASE_INSENSITIVE
Pattern.compile("regexStr", Pattern.CASE_INSENSITIVE)
// (?i) enables case-insensitivity
String regexStr = "(?i).*";
src.matches(regexStr);
// convert string cases
src.toLowerCase();
src.matches(regexStr);

Multiple flags

Pattern.compile("regexStr", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);

Create a Multi-Modules Project

  1. Create Parent Maven Project
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=parent-project -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
# or
$ mvn archetype:generate -DgroupId=com.taogen.demo -DartifactId=parent-project -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
  1. Create submodules
$ cd parent-project
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=web -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=service -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
  1. Configuring Parent Project’s pom.xml

3.1. Setting <packaging> to pom

$ cd parent-project
$ vim pom.xml

Add the following <packaging> configuration below the <artifactId> tag In the parent-project‘s pom.xml

<packaging>pom</packaging>

By setting the packaging to pom type, we’re declaring that the project will serve as a parent or an aggregator; it won’t produce further artifacts.

3.2. Configuring Parent Submodules

$ cd parent-project
$ vim pom.xml

Add the following <modules> configuration below the <version> tag In the parent-project‘s pom.xml

<modules>
<module>web</module>
<module>service</module>
</modules>
  1. Configuring Submodule Project’s pom.xml

In the individual submodules’ pom.xml, add the parent-project in the parent section.

$ vim web/pom.xml
$ vim service/pom.xml

Add the following <parent> configuration below the <moduleVersion> tag In the submodule project’s pom.xml

<parent>
<artifactId>parent-project</artifactId>
<groupId>com.taogen.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

Inherent Parent Dependencies Version

  1. Declaring dependencies version in the parent project pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20211205</version>
</dependency>
</dependencies>
</dependencyManagement>
  1. Inherent dependencies version in Submodules pom.xml

By declaring version in the parent, all submodules using only the groupId and artifactId, and the version will be inherited

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>

if a child module needs to use a different version of a managed dependency, you can override the managed version.

<dependencies>
...
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>xxx</version>
</dependency>
...
</dependencies>
  1. Verifying Inherent dependencies version is working
$ cd parent-project
$ mvn clean package

Dependency Between Submodules

In this tutorial, the web module depends on the service module. You can add service module dependency in web module project pom.xml.

<dependency>
<groupId>com.taogen.demo</groupId>
<artifactId>service</artifactId>
<version>${project.parent.version}</version>
</dependency>

Notice: To avoid a circular dependency between submodules. A circular dependency occurs when a module A depends on another module B, and the module B depends on module A.

Testing in Spring Boot Multi-Modules Maven Project

Test All Submodules

  1. Configuring spring boot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

or

<properties>
<!-- included spring-boot-starter-parent -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- custom properties -->
<spring-boot.version>2.6.4</spring-boot.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- included spring-boot-starter-parent -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<!-- included spring-boot-starter-parent. For unit testing. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
  1. Running all submodule unit tests
$ cd parent-project
$ mvn test

References

Gotenberg is a great open source project for converting files to PDF.

Starting Up Gotenberg

docker run --rm -p 3000:3000 gotenberg/gotenberg:7
# Run container in background
docker run --rm -d -p 3000:3000 gotenberg/gotenberg:7

View console output of Gotenberg

docker container ls
docker logs -f <containerId>

API Usage

Health Check

http://localhost:3000/health

Chromium

The Chromium module interacts with the Chromium browser to convert HTML documents to PDF.

Converting HTML URLs to PDF

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/url' \
--form 'url="https://my.url"' \
-o my.pdf

Converting local HTML files to PDF

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/html' \
--form 'files=@"/file/path/to/index.html"' \
-o my.pdf

LibreOffice

The LibreOffice module interacts with LibreOffice to convert documents to PDF.

To convert documents to PDF

Files with the following extensions:

.bib` `.doc` `.xml` `.docx` `.fodt` `.html` `.ltx` `.txt` `.odt` `.ott` `.pdb` `.pdf` `.psw` `.rtf` `.sdw` `.stw` `.sxw` `.uot` `.vor` `.wps` `.epub` `.png` `.bmp` `.emf` `.eps` `.fodg` `.gif` `.jpg` `.met` `.odd` `.otg` `.pbm` `.pct` `.pgm` `.ppm` `.ras` `.std` `.svg` `.svm` `.swf` `.sxd` `.sxw` `.tiff` `.xhtml` `.xpm` `.fodp` `.potm` `.pot` `.pptx` `.pps` `.ppt` `.pwp` `.sda` `.sdd` `.sti` `.sxi` `.uop` `.wmf` `.csv` `.dbf` `.dif` `.fods` `.ods` `.ots` `.pxl` `.sdc` `.slk` `.stc` `.sxc` `.uos` `.xls` `.xlt` `.xlsx` `.tif` `.jpeg` `.odp

Converting local document files to PDF

Relative file path

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"test.docx"' \
-o test.pdf

Absolute file path

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"D:\My Workspace\test.docx"' \
-o test.pdf

Multiple files

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"/path/to/file.docx"' \
--form 'files=@"/path/to/file.xlsx"' \
-o my.zip

Request by Java OkHttp

private static final String GOTENBERG_SERVICE_HOST = "http://xxx.xxx.xxx.xxx:3000";

private static final String CONVERT_TO_PDF_URI = "/forms/libreoffice/convert";

private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();

public static byte[] convertToPdfByGotenberg(InputStream inputStream, String fileName) throws IOException {
Request.Builder requestBuilder = new Request.Builder()
.url(HttpUrl.parse(GOTENBERG_SERVICE_HOST + CONVERT_TO_PDF_URI).newBuilder().build());
String mediaType = URLConnection.guessContentTypeFromName(fileName);
if (mediaType == null) {
mediaType = "application/octet-stream";
}
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("files", fileName,
RequestBody.create(MediaType.parse(mediaType),
IOUtils.toByteArray(inputStream)));
requestBuilder.post(builder.build());
try (Response response = OKHTTP_CLIENT.newCall(requestBuilder.build()).execute()) {
return response.body().bytes();
}
}

PDF Engines

Merge multiple PDF files to one PDF file.

curl \
--request POST 'http://localhost:3000/forms/pdfengines/merge' \
--form 'files=@"/path/to/pdf1.pdf"' \
--form 'files=@"/path/to/pdf2.pdf"' \
--form 'files=@"/path/to/pdf3.pdf"' \
--form 'files=@"/path/to/pdf4.pdf"' \
-o my.pdf

Webhook

The Webhook module provides a middleware that allows you to upload the output file from multipart/form-data routes to the destination of your choice.

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/url' \
--header 'Gotenberg-Webhook-Extra-Http-Headers: {"MyHeader": "MyValue"}' \
--header 'Gotenberg-Webhook-Url: https://my.webhook.url' \
--header 'Gotenberg-Webhook-Method: PUT' \
--header 'Gotenberg-Webhook-Error-Url: https://my.webhook.error.url' \
--header 'Gotenberg-Webhook-Error-Method: POST' \
--form 'url="https://my.url"'

The middleware reads the following headers:

  • Gotenberg-Webhook-Url - the callback to use - required
  • Gotenberg-Webhook-Error-Url - the callback to use if error - required
  • Gotenberg-Webhook-Method - the HTTP method to use (POST, PATCH, or PUT - default POST).
  • Gotenberg-Webhook-Error-Method - the HTTP method to use if error (POST, PATCH, or PUT - default POST).
  • Gotenberg-Webhook-Extra-Http-Headers - the extra HTTP headers to send to both URLs (JSON format).

Common Errors

Error: file name is too long

{"level":"error","ts":1649232587.172472,"logger":"api","msg":"create request context: copy to disk: create local file: open /tmp/9e10e36d-c5a9-4623-9fac-92db4a0d0982/xxx.doc: file name too long","trace":"de0b5ce2-5a99-406e-a61d-4abb65ef0294","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":500,"latency":13161094736,"latency_human":"13.161094736s","bytes_in":6983097,"bytes_out":21}

Solutions

Decreasing your file name length.

Error: file name contains UTF-8 characters

{"level":"error","ts":1649234692.9638329,"logger":"api","msg":"convert to PDF: unoconv PDF: unix process error: wait for unix process: exit status 6","trace":"28d9a196-10e5-4c7d-af6a-178494f49cd1","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":500,"latency":130617774,"latency_human":"130.617774ms","bytes_in":11559,"bytes_out":21}

Solutions

Encoding filename by URLEncoder.encode(fileName, "UTF-8").

Error: file extension name is not right

{"level":"error","ts":1649234777.9096093,"logger":"api","msg":"validate form data: no form file found for extensions: [.bib .doc .xml .docx .fodt .html .ltx .txt .odt .ott .pdb .pdf .psw .rtf .sdw .stw .sxw .uot .vor .wps .epub .png .bmp .emf .eps .fodg .gif .jpg .jpeg .met .odd .otg .pbm .pct .pgm .ppm .ras .std .svg .svm .swf .sxd .sxw .tif .tiff .xhtml .xpm .odp .fodp .potm .pot .pptx .pps .ppt .pwp .sda .sdd .sti .sxi .uop .wmf .csv .dbf .dif .fods .ods .ots .pxl .sdc .slk .stc .sxc .uos .xls .xlt .xlsx]","trace":"10e6ffd8-2d00-4374-b6fb-0c4ff6af3043","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":400,"latency":3885166,"latency_human":"3.885166ms","bytes_in":11551,"bytes_out":449}

Solutions

Files in form data should have a correct file extension.

References

Gotenberg Documentation

To get a good job, to become a good developer first.

Keep Learning

  • Reading textbooks, documentation.
  • Reading open-source code.
  • Writing blogs.

Practice Your Craft

  • Design yourself systems.
  • Build yourself systems.
  • Optimizes systems.
  • Solving coding problems on LeetCode.

Build Your Reputations

  • Contributing to open-source projects on GitHub.
  • Answering questions on Stack Overflow.
  • Keep writing technical blogs on your website.

What is Empty Cache and Hard Reload for Specific Domain

Clear current domain website cached static files, like HTML, CSS, JS, and images files.

Note: “Hard reload” is only clear current page cached static files not current domain. For current domain we need use “Empty Cache and Hard Reload”

Empty Cache and Hard Reload

Chrome

Method One

  1. Press control+shift+I or F12 to open Chrome Developer Tools.
  2. Right-click the reload button next to the address bar.
  3. Choose: “Empty Cache and Hard Reload”.

Method Two

  1. Press control+shift+I or F12 to open Chrome Developer Tools.
  2. Under Network (previously General) check Disable cache. (re-enable caching by un-checking this box)
  3. Reload the page.
0%