0
votes

I need to make an API request using feign. Method type: POST; Headers must include Content-Type = text/xml.

My code:

@Component
@ConfigurationProperties("client.machine")
class MachineProperties {
    lateinit var url: String
    lateinit var timeout: String
}

@Configuration
class MachineClientConfig(
    private val connectionProperties: MachineProperties,
    private val meterRegistry: MeterRegistry,
    private val objectMapper: ObjectMapper
) {
    @Bean
    fun machineApi() = Feign.builder()
        .addCapability(MicrometerCapability(meterRegistry))
        .retryer(DefaultEtsmIntegratonRetryer())
        .encoder(JacksonEncoder(objectMapper))
        .decoder(JacksonDecoder(objectMapper))
        .decode404()
        .logger(Slf4jLogger(MachineApi::class.java))
        .options(Request.Options(connectionProperties.timeout.toInt(), connectionProperties.timeout.toInt()))
        .target(MachineApi::class.java, connectionProperties.url)!!
}

@Headers(
    HttpHeaders.ACCEPT + ": " + MediaType.TEXT_XML_VALUE,
    HttpHeaders.CONTENT_TYPE + ": " + MediaType.TEXT_XML_VALUE
)
interface MachineApi {
    @RequestLine("POST /api/v1/charge")
    fun sendRequest(body: String): MachineResponse
}

@Service
class MachineClient(private val machineApi: MachineApi) {
    fun sendRequest(body: String, dealId: Long): MachineResponse {
        return machineApi.sendRequest(body)
    }
}

public class JacksonEncoder implements Encoder {

  private final ObjectMapper mapper;

  public JacksonEncoder() {
    this(Collections.<Module>emptyList());
  }

  public JacksonEncoder(Iterable<Module> modules) {
    this(new ObjectMapper()
        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
        .configure(SerializationFeature.INDENT_OUTPUT, true)
        .registerModules(modules));
  }

  public JacksonEncoder(ObjectMapper mapper) {
    this.mapper = mapper;
  }

  @Override
  public void encode(Object object, Type bodyType, RequestTemplate template) {
    try {
      JavaType javaType = mapper.getTypeFactory().constructType(bodyType);
      template.body(mapper.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8);
    } catch (JsonProcessingException e) {
      throw new EncodeException(e.getMessage(), e);
    }
  }
}

When I call the sendRequest method from the MachineClient class I get an error like this:

feign.FeignException$Forbidden: [403 Forbidden] during [POST] to [https://dev.ed.com/tsm/v1/charge] [MachineApi#sendRequest(String)]: [{
    "httpCode": "403",
    "moreInformation": "SQL-Injection Error"
}]

But when I try to make a call through curl it works great.

curl --location --request POST 'https://dev.ed.com/tsm/v1/charge' \
--header 'Content-Type: text/xml' \
--data-raw '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ChargeRq>...</ChargeRq>'

What am I missing in my code and why am I getting an error? Help please, I have no more ideas

1
what is the value of body variable? you can print and check. also, I see the content-type is missing, which is present in curl requestsidgate
@sidgate body is xml string that is generated by Marshaller from JAXBContext (javax.xml.bind). After generation, I print this xml in the console and use it when I check it through curl (I just copy it from the console and paste it into Postman).NeverSleeps
@sidgate I tried to send an empty string "", got the same error. Then I don't understand why the value is not sent :-(NeverSleeps

1 Answers

0
votes

The solution was as follows:

Since Content -Type = "text / xml" is required, I need to use JAXBEncoder. In this case, I need to send not a String, but a ChargeRqType - an object that was received using jaxb.

@Component
@ConfigurationProperties("client.machine")
class MachineProperties {
    lateinit var url: String
    lateinit var timeout: String
}

@Configuration
class MachineClientConfig(
    private val connectionProperties: MachineProperties,
    private val meterRegistry: MeterRegistry,
    private val jaxbContextFactory: JAXBContextFactory,
    private val objectMapper: ObjectMapper
) {
    @Bean
    fun creditMachineApi() = Feign.builder()
        .addCapability(MicrometerCapability(meterRegistry))
        .retryer(DefaultEtsmIntegratonRetryer())
        .encoder(JAXBEncoder(jaxbContextFactory))
        .decoder(JacksonDecoder(objectMapper))
        .logger(Slf4jLogger(MachineApi::class.java))
        .options(Request.Options(connectionProperties.timeout.toInt(), connectionProperties.timeout.toInt()))
        .target(MachineApi::class.java, connectionProperties.url)!!
}

@Headers(
    HttpHeaders.ACCEPT + ": " + MediaType.TEXT_XML_VALUE,
    HttpHeaders.CONTENT_TYPE + ": " + MediaType.TEXT_XML_VALUE
)
interface MachineApi {
    @RequestLine("POST /tsm/v1/charge")
    fun sendRequest(body: ChargeRqType): MachineResponse
}

@Service
class MachineClient(private val machineApi: MachineApi) {

    fun sendRequest(body: ChargeRqType, dealId: Long) =
        machineApi.sendRequest(body)
}

@Configuration
class JAXBConfig {
    @Bean
    fun jaxbContextFactory() = JAXBContextFactory.Builder()
        .withMarshallerJAXBEncoding("UTF-8")
        .withMarshallerSchemaLocation(ClassPathResource("xsd/SrvCreateApp03.xsd").path)
        .build()
}