Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java client throws IOException for 201 responses due to missing body #136

Open
steven-barnes opened this issue Nov 15, 2024 · 7 comments
Open

Comments

@steven-barnes
Copy link

steven-barnes commented Nov 15, 2024

        Account body = new Account();
        body.setName("test3");
        body.setCurrency(Currency.USD);
        body.setExternalKey("89f20977-f937-4bd7-82f9-ac4ef07b9999");
        body.setEmail("test3@comcast.com");
        accountApi.createAccount(body, requestOptions);

This results in an IOException, as there is no response body. Running the code again results in a 409 exception, Account already exists for key 89f20977-f937-4bd7-82f9-ac4ef07b9999, indicating the account was successful created.

When creating similar accounts in the swagger page, I see the response code is 201, but the server does not return a body. The swagger page suggests that an Account object will be returned. This conflicts with the information in the docs, which says that the endpoint will return a URL in the location header.

curl -X POST "https://killbill.dev.commerce.comcast.com/1.0/kb/accounts" -H "accept: application/json" -H "X-Killbill-CreatedBy: testing" -H "authorization: Basic REDACTED" -H "X-Killbill-ApiKey: REDACTED" -H "X-Killbill-ApiSecret: REDACTED" -H "Content-Type: application/json" -d "{ \"name\": \"test99\", \"externalKey\": \"89f20977-f937-4bd7-82f9-ac4ef0999999\", \"email\": \"test99@comcast.com\", \"currency\": \"USD\"}"
@pierre
Copy link
Member

pierre commented Nov 16, 2024

Indeed, Kill Bill doesn't return the body upon creation (https://docs.killbill.io/latest/quick_start_with_kb_api#_step_3_create_an_account), but you can ask the client to fetch it automatically using requestOptions.withFollowLocation(true):

if (requestOptions.shouldFollowLocation() && response.headers().firstValue("Location").isPresent()) {

IIRC we could never really tell Swagger about this behaviour.

@steven-barnes
Copy link
Author

I have tried that, and still get an IOException:

RequestOptions.builder
      .withCreatedBy("CS2")
      .withFollowLocation(Boolean.TRUE)
      .build()

Looking at the code for createAccount, it seems to be setting followLocation to TRUE by default.

@pierre
Copy link
Member

pierre commented Nov 18, 2024

Are you following https://docs.killbill.io/latest/java_client ?

If so, could you share a Main class that reproduces the issue on a vanilla installation? The Java client is used pervasively throughout our test suite, and I don't know of any regression.

@steven-barnes
Copy link
Author

I am using the latest version in Maven Central, 1.3.6. I have coded a test in Java, and still get an IOException:

package org.example;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.client.*;
import org.killbill.billing.client.api.gen.*;
import org.killbill.billing.client.model.gen.Account;

public class Main {

    static KillBillHttpClient client = new KillBillHttpClient(REDACTED);

    static AccountApi accountApi = new AccountApi(client);

    public static void main(String[] args) {
        var requestOptions = RequestOptions.builder()
                .withCreatedBy("CS2")
                .withFollowLocation(Boolean.TRUE)
                .build();

        var body = new Account();
        body.setName("test");
        body.setCurrency(Currency.USD);
        body.setExternalKey("xyzzy2");
        body.setEmail("test@comcast.com");

        try {
            var result = accountApi.createAccount(body, requestOptions);
            System.out.println(result);
        } catch (KillBillClientException e) {
            e.printStackTrace();
        }
    }
}

@pierre
Copy link
Member

pierre commented Nov 19, 2024

Unable to reproduce.

  • Can you check what's different with the below?
  • Can you check the Kill Bill logs?

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>my-app</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>11</maven.compiler.release>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.kill-bill.billing</groupId>
      <artifactId>killbill-client-java</artifactId>
      <version>1.3.6</version>
    </dependency>
  </dependencies>
  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to
      parent pom) -->
      <plugins>
        <!-- clean lifecycle, see
        https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.4.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see
        https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.3.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.13.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.3.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.4.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>3.1.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>3.1.2</version>
        </plugin>
        <!-- site lifecycle, see
        https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.12.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.6.1</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.mycompany.app.App</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>single</goal>
            </goals>
            <phase>package</phase>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

App.java:

package com.mycompany.app;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.*;
import org.killbill.billing.client.api.gen.*;
import org.killbill.billing.client.model.gen.Account;

public class App {
    public static void main(String[] args) {
        String username = "admin";
        String password = "password";
        String apiKey = "bob";
        String apiSecret = "lazar";
        String serverHost = "localhost";
        int serverPort = 8080;
        String kbServerUrl = String.format("http://%s:%d", serverHost, serverPort);
        KillBillHttpClient killBillHttpClient = new KillBillHttpClient(kbServerUrl, username, password, apiKey,
                apiSecret);
        AccountApi accountApi = new AccountApi(killBillHttpClient);

        var requestOptions = RequestOptions.builder()
                .withCreatedBy("CS2")
                .withFollowLocation(Boolean.TRUE)
                .build();

        var body = new Account();
        body.setName("test");
        body.setCurrency(Currency.USD);
        body.setExternalKey("xyzzy");
        body.setEmail("test@comcast.com");

        try {
            var result = accountApi.createAccount(body, requestOptions);
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Execution:

$ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
class Account {
    org.killbill.billing.client.model.gen.Account@fc5e4c1c
    accountId: 5dbaf858-5447-4262-8418-ccae0e26ca31
    name: test
    firstNameLength: null
    externalKey: xyzzy
    email: test@comcast.com
    billCycleDayLocal: 0
    currency: USD
    parentAccountId: null
    isPaymentDelegatedToParent: false
    paymentMethodId: null
    referenceTime: 2024-11-19T08:14:40.000Z
    timeZone: UTC
    address1: null
    address2: null
    postalCode: null
    company: null
    city: null
    state: null
    country: null
    locale: null
    phone: null
    notes: null
    isMigrated: null
    accountBalance: null
    accountCBA: null
    auditLogs: []
}
$ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
org.killbill.billing.client.KillBillClientException: Account already exists for key xyzzy
        at org.killbill.billing.client.KillBillHttpClient.throwExceptionOnResponseError(KillBillHttpClient.java:390)
        at org.killbill.billing.client.KillBillHttpClient.doPrepareRequestInternal(KillBillHttpClient.java:349)
        at org.killbill.billing.client.KillBillHttpClient.doPrepareRequest(KillBillHttpClient.java:296)
        at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:210)
        at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:205)
        at org.killbill.billing.client.api.gen.AccountApi.createAccount(AccountApi.java:176)
        at com.mycompany.app.App.main(App.java:34)

Java version:

$ java -version
openjdk version "11.0.14.1" 2022-02-08 LTS
OpenJDK Runtime Environment Microsoft-31205 (build 11.0.14.1+1-LTS)
OpenJDK 64-Bit Server VM Microsoft-31205 (build 11.0.14.1+1-LTS, mixed mode)

@steven-barnes
Copy link
Author

Using the debugger, I can see that the location URL is using http, the request to killbill is using https:

    static KillBillHttpClient client = new KillBillHttpClient(
            "https://killbill.dev.commerce.comcast.com",

http://killbill.dev.commerce.comcast.com:80/1.0/kb/accounts/16c15fcb-f461-4ae0-baff-ec114883496a

We are self-hosting KB in AWS, and I am testing from my laptop.

@pierre
Copy link
Member

pierre commented Nov 20, 2024

Is the Location header returning HTTP instead of HTTPS? What is terminating SSL, Tomcat or an intermediate load balancer? If the latter, you might need to configure the load balancer to send x-forwarded-proto.

Related:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants