This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 439ff3ce71 [core] Support text/plain content type for REST catalog 
request body (#8198)
439ff3ce71 is described below

commit 439ff3ce71a3ce8d31195079cfedad9069579836
Author: Arnav Balyan <[email protected]>
AuthorDate: Sat Jun 20 01:18:28 2026 -0700

    [core] Support text/plain content type for REST catalog request body (#8198)
    
    - REST `create table` fails because post and delete are sent without a
    content type, defaulting to plain text, which JSON expecting REST
    servers reject before processing.
    - Fix by setting the request body content type to json in the http
    client, and by body type in the simple client, (form encoded for form
    bodies and JSON otherwise).
---
 .../src/main/java/org/apache/paimon/rest/HttpClient.java   |  5 +++--
 .../main/java/org/apache/paimon/rest/SimpleHttpClient.java |  7 ++++++-
 .../test/java/org/apache/paimon/rest/HttpClientTest.java   | 14 ++++++++++++++
 3 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/HttpClient.java 
b/paimon-api/src/main/java/org/apache/paimon/rest/HttpClient.java
index 5b3c481c12..2477ff9db6 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/HttpClient.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/HttpClient.java
@@ -33,6 +33,7 @@ import org.apache.hc.client5.http.classic.methods.HttpGet;
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
 import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicHeader;
@@ -94,7 +95,7 @@ public class HttpClient implements RESTClient {
         HttpPost httpPost = new HttpPost(getRequestUrl(path, null));
         String encodedBody = RESTUtil.encodedBody(body);
         if (encodedBody != null) {
-            httpPost.setEntity(new StringEntity(encodedBody));
+            httpPost.setEntity(new StringEntity(encodedBody, 
ContentType.APPLICATION_JSON));
         }
         Header[] authHeaders = getHeaders(path, "POST", encodedBody, 
restAuthFunction);
         httpPost.setHeaders(authHeaders);
@@ -112,7 +113,7 @@ public class HttpClient implements RESTClient {
         HttpDelete httpDelete = new HttpDelete(getRequestUrl(path, null));
         String encodedBody = RESTUtil.encodedBody(body);
         if (encodedBody != null) {
-            httpDelete.setEntity(new StringEntity(encodedBody));
+            httpDelete.setEntity(new StringEntity(encodedBody, 
ContentType.APPLICATION_JSON));
         }
         Header[] authHeaders = getHeaders(path, "DELETE", encodedBody, 
restAuthFunction);
         httpDelete.setHeaders(authHeaders);
diff --git 
a/paimon-api/src/main/java/org/apache/paimon/rest/SimpleHttpClient.java 
b/paimon-api/src/main/java/org/apache/paimon/rest/SimpleHttpClient.java
index c2e044d2cc..04aaf3bfea 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/SimpleHttpClient.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/SimpleHttpClient.java
@@ -24,6 +24,7 @@ import org.apache.hc.client5.http.classic.methods.HttpGet;
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicHeader;
@@ -55,7 +56,11 @@ public class SimpleHttpClient implements Closeable {
         }
         String encodedBody = RESTUtil.encodedBody(body);
         if (encodedBody != null) {
-            httpPost.setEntity(new StringEntity(encodedBody));
+            ContentType contentType =
+                    body instanceof Map
+                            ? ContentType.APPLICATION_FORM_URLENCODED
+                            : ContentType.APPLICATION_JSON;
+            httpPost.setEntity(new StringEntity(encodedBody, contentType));
         }
 
         return exec(httpPost);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java
index 6cf9f44802..a742419037 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java
@@ -27,6 +27,7 @@ import org.apache.paimon.rest.responses.ErrorResponse;
 
 import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
 
+import okhttp3.mockwebserver.RecordedRequest;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,6 +38,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -261,4 +263,16 @@ public class HttpClientTest {
                     "Error message should contain the original non-JSON 
response");
         }
     }
+
+    @Test
+    public void testPostSetsJsonContentType() throws Exception {
+        server.enqueueResponse(mockResponseDataStr, 200);
+        httpClient.post(MOCK_PATH, mockResponseData, MockRESTData.class, 
restAuthFunction);
+        RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS);
+        String contentType = request.getHeader("Content-Type");
+        Assertions.assertNotNull(contentType, "POST request must carry a 
Content-Type header");
+        Assertions.assertTrue(
+                contentType.contains("application/json"),
+                "POST body must be sent as application/json, but was: " + 
contentType);
+    }
 }

Reply via email to