Ktor client Auth feature does not sending Authorization header - kotlin

I am trying to use ktor client in Kotlin/MPP (Multiplatform) project and on JVM target feature basic authentication does not seem to have an effect.
Here is an example to reproduce:
import io.ktor.client.HttpClient
import io.ktor.client.features.ResponseException
import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.header
import kotlinx.coroutines.runBlocking
import java.util.*
fun main() = runBlocking {
val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Auth) {
basic {
username = "user"
password = "pass"
}
}
}
val url = "https://en.wikipedia.org/wiki/Main_Page"
val failing = try {
client.get<String>(url)
} catch (e: ResponseException) {
"failed"
}
val succeeding = try {
client.get<String>(url) {
header("Authorization", "Basic ${Base64.getEncoder().encodeToString("user:pass".toByteArray())}")
}
} catch (e: ResponseException) {
"failed"
}
}
Observation
From the logger output, you can see that client does not send Authorization header but I experience no problems when I provide such header manually:
First request (failing example:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Second request (succeeding example:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Environment
Kotlin: 1.4-M1
Ktor Artifacts version 1.3.1:
ktor-client-core
ktor-client-logging
ktor-client-json
ktor-client-serialization
ktor-client-auth-basic
Did I miss something?

Please add sendWithoutRequest = true
1.x https://api.ktor.io/1.3.1/io.ktor.client.features.auth.providers/-basic-auth-config/send-without-request.html
install(Auth) {
basic {
sendWithoutRequest = true
username = "user"
password = "pass"
}
}
2.x https://ktor.io/docs/basic-client.html#configure
install(Auth) {
basic {
sendWithoutRequest { true }
credentials {
BasicAuthCredentials(
username = "user",
password = "pass",
)
}
}
}
Result:
sending with sendWithoutRequest set to true
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Explanation:
By default, Ktor will wait for the server to respond with 401,
Unauthorized, and only then send the authentication header. In your
example, wiki never responds with a 401, as it is not a protected
resource. Therefore, adding sendWithoutRequest is required. If you
tried with some url that does respond with a 401, you would see that
Ktor will then send a second request (after receiving 401) with the
authentication header. You can try with this url to see -
https://api.sumologic.com/api/v1/collectors.
This is the logging when done against that protected api with sendWithoutRequest turned off, your original input. As you can see, there are now 2 requests made, the first without the authorization header, and then the second one, with the authorization header, after the server has responded with a 401.
sending with sendWithoutRequest set to false and hitting a protected resource
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Note: I just saw a comment by Andylamax that a new version "fixes" it. Perhaps, I don't know as I haven' tried with that new version. But I would like to add that this is not something unique to Ktor, and at least in this respect is not a bug (but maybe they changed their minds? Again, I don't know). In fact, it is my experience with C# that led me to suspect what's going in here and find the answer. The WebRequest in C# behaves the same way, you need to set PreAuthenticate to true to send the credentials immediately. See here https://learn.microsoft.com/en-us/dotnet/api/system.net.webrequest.preauthenticate?view=netcore-3.1.

Ktor 2.1.0
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
private val httpClient = HttpClient(CIO) {
install(Auth) {
basic {
credentials {
BasicAuthCredentials(
username = "user",
password = "pass"
)
}
}
}
}
build.gradle
implementation("io.ktor:ktor-client-core:2.1.0")
implementation("io.ktor:ktor-client-cio:2.1.0")
implementation("io.ktor:ktor-client-auth:2.1.0")

Related

Duplicate headers are added at runtime in Rest assured

I am trying to send a request with one header(authorization) but when i check the logs i am seeing same header is added twice to the request which is causing my tests to fail.
Below is the post method which i am using
public static Response doPost(String basePath, Object payload, String header) {
return reqSpec.given().header("X-HSBC-E2E-Trust-Token", header).config(RestAssuredConfig.config())
.body(payload.toString()).log().all()
.post(basePath).then().extract().response();
}
Log:
Request URI: https:Url
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: <none>
Headers:E2E-Trust-Token=0JDX09VRIMl9TRVJWRVJfREVWIn0.eyJzaXQiOiJhZDp1c3I6ZW1wbG95ZWVJZCIsInN1YiI6IkdCLVNWQy1GV0tUQVBJRlAiLCJhbHQiOlt7InNpdCI6ImVtYWlsIiwic3ViIjoiR0ItU1ZDLUZXS1RBUElGUEBOb3RSZWNlaXZpbmdNYWlmhzYmMuY29tIn0seyJzaXQiOiJuYW1lIiwic3ViIjoiR0ItU1ZDLUZXS1RBUElGUCJ9LHsic2l0IjoibG9naW5JZCIsInN1YiI6IkdCLVNWQy1GV0tUQVBJRlAifV0sImdycCI6WyJDTj1JbmZvZGlyLUFQSUF1dG9GV0stUHJvZFN W5kYXJkLE9VPUlUSURBUEksT1U9QXBwbGljYXRpb25zLE9VPUdyb3VwcyxEQz1JbmZvRGlyLERDPVByb2QsREM9SFNCQyIsIkNOPUluZm9kaXItQVBJQXV0b0ZXSy1Qcm9kQWRtaW4sT1U9SVRJREFQSSxPVT1BcHBsaWNhdGlvbnM1U9R3JvdXBzLERDPUluZm9EaXIsREM9UHJvZCxEQz1IU0JDIl0sInNjb3BlIjoiSVRJREFQSSIsImp0aSI6IjBlMGQ0YzM5LThlMjQtNDI5MC1iZGExLTE0MzllOTRlMzcxYyIsImlzcyI6ImdibDIwMTA5MDk2LmhjLmNsb3VkLnVmhzYmMiLCJpYXQiOjE2NTc1MzE2NTEsImV4cCI6MTY1NzUzMjI1MSwiYXVkIjoiR0ItU1ZDLUFQSUZXREVWQEhCRVUiLCJ1c2VyX25hbWUiOiJHQi1TVkMtRldLVEFQSUZQIn0.HgxR7j7fbl5JRQxTFX0Z7OJLk_11Vc4fUFPEZ9E
Accept=*/*
E2E-Trust-Token=0JDX09BVVRIMl9TRVJWRVJfREVWIn0.eyJzaXQiOiJhZDp1c3I6ZW1wbG95ZWVJZCIsInN1YiI6IkdCLVNWQy1GV0tUQVBJRlAiLCJhbHQiOlt7InNpdCI6ImVtYWlsIiwic3ViIjoiR0ItU1ZDLUZXS1RBUElGUEBOb3RSZWNlaXZmdNYWlsLmhzYmMuY29tIn0seyJzaXQiOiJuYW1lIiwic3ViIjoiR0ItU1ZDLUZXS1RBUElGUCJ9LHsic2l0IjoibG9naW5JZCIsInN1YiI6IkdCLVNWQy1GV0tUQVBJRlAifV0sImdycCI6WyJDTj1JbmZvZGlyLUFQSUF1dG9GV0sHJvZFN0YW5kYXJkLE9VPUlUSURBUEksT1U9QXBwbGljYXRpb25zLE9VPUdyb3VwcyxEQz1JbmZvRGlyLERDPVByb2QsREM9SFNCQyIsIkNOPUluZm9kaXItQVBJQXV0b0ZXSy1Qcm9kQWRtaW4sT1U9SVRJREFQSSxPVT1BcHBsaWN
3VkLnVrLmhzYmMiLCJpYXQiOjE2NTc1MzE2NTEsImV4cCI6MTY1NzUzMjI1MSwiYXVkIjoiR0ItU1ZDLUFQSUZXREVWQEhCRVUiLCJ1c2VyX25hbWUiOiJHQi1TVkMtRldLVEFQSUZQIn0.HgxR7j7fbl5JRQxTFX0Z7OJLk_11Vc4FPEZ9Ec2xVXmUxeE6M3f8wBe4HOrQDJw_gWm_Kvf_hepsvAu0S0ACRQ3P_4i5yf2z0FXtBoUd3v9AvZcB299PZGJhNZ4HxUEP5SdzjQWOuZgdHAFg6GJdgsYBLTuCXQqy-kdqgPczgx7bgKTBfqrWH2I4qH1ZSE2OlksvGPj0Ia1Ts
Content-Type=application/json; charset=UTF-8
Cookies: <none>
Multiparts: <none>
Body:
{
"name": "TestRole11072733",
"requiresApproval": true,
"signatures": [
1091
]
}
10:27:36.574 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Get connection for route {s}->https:URL
10:27:36.574 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnectionOperator - Connecting to Endpoint
10:27:37.001 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: ignoreCookies
10:27:37.001 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
10:27:37.001 [main] DEBUG org.apache.http.client.protocol.RequestProxyAuthentication - Proxy auth state: UNCHALLENGED
10:27:37.001 [main] DEBUG org.apache.http.impl.client.DefaultHttpClient - Attempt 1 to execute request
10:27:37.001 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnection - Sending request: POST Endpoint

Can't mount routes, even though registerRoute function is called

I'm trying to write a simple Ktor app. I've got a file called ImageRoutes.kt:
package my.keddad.routes
// some imports here
fun Route.imageRouting() {
route("/image") {
post {
// some logic here
}
}
}
fun Application.registerImageRoutes() {
routing {
imageRouting()
}
}
I want to mount those routes. To do so, I add my registerImageRoutes() function to embeddedServer configuration. My Application.kt:
package my.keddad
import io.ktor.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import my.keddad.plugins.*
import my.keddad.routes.registerImageRoutes
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
configureRouting()
configureHTTP()
configureMonitoring()
configureSerialization()
registerImageRoutes()
}.start(wait = true)
}
When I start my code, registerImageRoutes function is called, but, apperently, no routes are added - I get 404 errors when trying to use them:
2021-12-01 00:45:37.015 [main] INFO ktor.application - Autoreload is disabled because the development mode is off.
2021-12-01 00:45:37.083 [main] INFO ktor.application - Responding at http://0.0.0.0:8080
2021-12-01 00:45:37.083 [main] INFO ktor.application - Application started in 0.079 seconds.
2021-12-01 00:45:37.084 [main] INFO ktor.application - Application started: io.ktor.application.Application#1a677343
2021-12-01 00:45:39.699 [eventLoopGroupProxy-4-1] INFO ktor.application - 404 Not Found: post - /image
Documentation also suggests adding my route registration function to Application.module, however, that also doesn't work. What am I doing wrong?
Here is how my trace looks like:
2021-12-01 10:02:54.375 [eventLoopGroupProxy-4-1] TRACE ktor.application - Trace for [image]
/, segment:0 -> SUCCESS # /
/image, segment:1 -> SUCCESS # /image
/image/(method:POST), segment:1 -> FAILURE "Selector didn't match" # /image/(method:POST)
Matched routes:
No results
Route resolve result:
FAILURE "No matched subtrees found" # /
I'm not sure what that means. It does know that there is a /image route registered, but it can't find the handler for POST to that URI?
Actually, it looks like it has something to do with request type. When running tests, like this:
withTestApplication({ configureRouting() }) {
with(handleRequest(HttpMethod.Post, "/image"){
...
}
I get 404. When using curl like this:
curl -v -X post http://localhost:8080/image
I also get 404. However, when using curl with "post" in capital letters, it does work as expected:
curl -v -X POST http://localhost:8080/image
I'm also confused by the trace, which looks just like the trace for failed requests (with the exception of it actually finding the route):
2021-12-01 11:21:33.369 [eventLoopGroupProxy-4-2] TRACE ktor.application - Trace for [image]
/, segment:0 -> SUCCESS # /
/image, segment:1 -> SUCCESS # /image
/image/(method:POST), segment:1 -> SUCCESS # /image/(method:POST)
Matched routes:
"" -> "image" -> "(method:POST)"
Route resolve result:
SUCCESS # /image/(method:POST)
Ok, the routes were actually mounted just fine. The problem was with how I tested my code - I didn't add registration functions to withTestApplication, and that caused the tests to fail.

Karate: How to test multipart form-data endpoint? [duplicate]

This question already has an answer here:
How to upload CSV file as a post request in Karate 0.9.0 version?
(1 answer)
Closed 2 years ago.
I have an file upload endpoint (/document) in a controller defined as follows:
#RestController
public class FileUploadController {
#Autowired
private PersonCSVReaderService personCSVReaderService;
#PostMapping(value = "/document", consumes= {MediaType.MULTIPART_FORM_DATA_VALUE})
public void handleFileUpload3(#RequestPart("file") MultipartFile file, #RequestPart("metadata") DocumentMetadata metadata) {
System.out.println(String.format("uploading file %s of %s bytes", file.getOriginalFilename(), file.getSize()));
personCSVReaderService.readPersonCSV(file, metadata);
}
}
I can test this endpoint using Advanced Rest Client (ARC) or Postman by defining the "file" part referencing the people.csv file and a text part specifying some sample metadata JSON.
Everything works fine and I get a 200 status back with the people.csv file contents being written to the console output by the service method:
uploading file people.csv of 256 bytes
{Address=1, City=2, Date of Birth=6, Name=0, Phone Number=5, State=3, Zipcode=4}
Person{name='John Brown', address='123 Main St.', city='Scottsdale', state='AZ', zipcode='85259', phoneNumber='555-1212', dateOfBirth='1965-01-01'}
Person{name='Jan Black', address='456 University Dr.', city='Atlanta', state='GA', zipcode='30306', phoneNumber='800-1111', dateOfBirth='1971-02-02'}
Person{name='Mary White', address='789 Possum Rd.', city='Nashville', state='TN', zipcode='37204', phoneNumber='888-2222', dateOfBirth='1980-03-03'}
Now, I want to run this as an automated Karate test. I have specified a MockConfig :
#Configuration
#EnableAutoConfiguration
#PropertySource("classpath:application.properties")
public class MockConfig {
// Services ...
#Bean
public PersonCSVReaderService personCSVReaderService() {
return new PersonCSVReaderService();
}
// Controllers ...
#Bean
public FileUploadController fileUploadController() {
return new FileUploadController();
}
}
I also have a MockSpringMvcServlet in the classpath and my karate-config.js is :
function fn() {
var env = karate.env; // get system property 'karate.env'
if (!env) {
env = 'dev';
}
karate.log('karate.env system property was:', env);
var config = {
env: env,
myVarName: 'someValue',
baseUrl: 'http://localhost:8080'
}
if (env == 'dev') {
var Factory = Java.type('MockSpringMvcServlet');
karate.configure('httpClientInstance', Factory.getMock());
//var result = karate.callSingle('classpath:demo/headers/common-noheaders.feature', config);
} else if (env == 'stg') {
// customize
} else if (env == 'prod') {
// customize
}
return config;
}
Other karate tests run ok using the mock servlet.
However, when I try this test to test the /document endpoint:
Feature: file upload end-point
Background:
* url baseUrl
* configure lowerCaseResponseHeaders = true
Scenario: upload file
Given path '/document'
And header Content-Type = 'multipart/form-data'
And multipart file file = { read: 'people.csv', filename: 'people.csv', contentType: 'text/csv' }
And multipart field metadata = { name: "joe", description: "stuff" }
When method post
Then status 200
I get this error:
16:14:42.674 [main] INFO com.intuit.karate - karate.env system property was: dev
16:14:42.718 [main] INFO o.s.mock.web.MockServletContext - Initializing Spring DispatcherServlet ''
16:14:42.719 [main] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet ''
16:14:43.668 [main] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$a4c7d08f] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
16:14:43.910 [main] INFO o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.14.Final
16:14:44.483 [main] INFO o.s.s.c.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
16:14:44.968 [main] INFO o.s.b.a.e.web.EndpointLinksResolver - Exposing 2 endpoint(s) beneath base path '/actuator'
16:14:45.006 [main] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 2287 ms
16:14:45.066 [main] INFO c.i.k.mock.servlet.MockHttpClient - making mock http client request: POST - http://localhost:8080/document
16:14:45.085 [main] DEBUG c.i.k.mock.servlet.MockHttpClient -
1 > POST http://localhost:8080/document
1 > Content-Type: multipart/form-data
16:14:45.095 [main] ERROR com.intuit.karate - http request failed: null
file-upload.feature:13 - null
HTML report: (paste into browser to view) | Karate version: 0.9.2
I can only assume that the arguments did not conform to what my endpoint was expecting - I never entered the endpoint in debug mode.
I tried this:
And multipart file file = read('people.csv')
And multipart field metadata = { name: "joe", description: "stuff" }
But that was a non-starter as well.
What am I doing wrong? The people.csv is in the same folder as fileupload.feature, so I am assuming it is finding the file. I also looked at upload.feature file in the Karate demo project given here:
Karate demo project upload.feature
But I could not make it work. Any help appreciated. Thanks in advance.
The Postman request looks like this:
EDIT UPDATE:
I could not get the suggested answer to work.
Here is the feature file:
Feature: file upload
Background:
* url baseUrl
* configure lowerCaseResponseHeaders = true
Scenario: upload file
Given path '/document'
And header Content-Type = 'multipart/form-data'
* def temp = karate.readAsString('people.csv')
* print temp
And multipart file file = { value: '#(temp)', filename: 'people.csv', contentType: 'text/csv' }
And multipart field metadata = { value: {name: 'joe', description: 'stuff'}, contentType: 'application/json' }
When method post
Then status 200
And here is the console output from running that test:
09:27:22.051 [main] INFO com.intuit.karate - found scenario at line: 7 - ^upload file$
09:27:22.156 [main] INFO com.intuit.karate - karate.env system property was: dev
09:27:22.190 [main] INFO o.s.mock.web.MockServletContext - Initializing Spring DispatcherServlet ''
09:27:22.190 [main] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet ''
09:27:23.084 [main] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$a4c7d08f] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
09:27:23.327 [main] INFO o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.14.Final
09:27:23.896 [main] INFO o.s.s.c.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
09:27:24.381 [main] INFO o.s.b.a.e.web.EndpointLinksResolver - Exposing 2 endpoint(s) beneath base path '/actuator'
09:27:24.418 [main] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 2228 ms
09:27:24.435 [main] INFO com.intuit.karate - [print] Name,Address,City,State,Zipcode,Phone Number,Date of Birth
John Brown,123 Main St.,Scottsdale,AZ,85259,555-1212,1965-01-01
Jan Black,456 University Dr.,Atlanta,GA,30306,800-1111,1971-02-02
Mary White,789 Possum Rd.,Nashville,TN,37204,888-2222,1980-03-03
09:27:24.482 [main] INFO c.i.k.mock.servlet.MockHttpClient - making mock http client request: POST - http://localhost:8080/document
09:27:24.500 [main] DEBUG c.i.k.mock.servlet.MockHttpClient -
1 > POST http://localhost:8080/document
1 > Content-Type: multipart/form-data
09:27:24.510 [main] ERROR com.intuit.karate - http request failed: null
file-upload.feature:14 - null
HTML report: (paste into browser to view) | Karate version: 0.9.2
Note: people.csv file reads successfully and prints in console.
Refer to this part of the docs: https://github.com/intuit/karate#read-file-as-string
So make this change:
* def temp = karate.readAsString('people.csv')
And multipart file file = { value: '#(temp)', filename: 'people.csv', contentType: 'text/csv' }
EDIT: my bad, also refer: https://github.com/intuit/karate#multipart-file
Feature: upload csv
Background: And def admin = read('classpath:com/project/data/adminLogin.json')
Scenario:
Given url baseUrl
And header Authorization = admin.token
And multipart file residentDetails = { read:'classpath:com/project/data/ResidentApp_Details.csv', filename: 'ResidentApp_Details.csv' }
When method POST
Then status 200
Note: Add only one extra line i.e And multipart file residentDetails = { read:'classpath:com/project/data/ResidentApp_Details.csv', filename: 'ResidentApp_Details.csv' }

How to configure spring-cloud-gateway to use sleuth to log request/response body

I am looking to develop a gateway server based on spring-cloud-gateway:2.0.2-RELEASE and would like to utilize sleuth for logging purposes. I have sleuth running since when I write to the log I see the Sleuth details (span Id, etc), but I an hoping to see the body of messages being logged automatically. Is there something I need to do to get Sleuth to log request/response out of the box with Spring-Cloud-Gateway?
Here is the request headers that arrive at my downstream service
headers:
{ 'x-request-foo': '2a9c5e36-2c0f-4ad3-926c-cb20d4428462',
forwarded: 'proto=http;host=localhost;for="0:0:0:0:0:0:0:1:51720"',
'x-forwarded-for': '0:0:0:0:0:0:0:1',
'x-forwarded-proto': 'http',
'x-forwarded-port': '80',
'x-forwarded-host': 'localhost',
'x-b3-traceid': '5bd33eb8050c7a32dfce6adfe68b06ca',
'x-b3-spanid': 'ba202a6d6f3e2893',
'x-b3-parentspanid': 'dfce6adfe68b06ca',
'x-b3-sampled': '0',
host: 'localhost:8080' },
Gradle file in the gateway service..
buildscript {
ext {
kotlinVersion = '1.2.61'
springBootVersion = '2.0.6.RELEASE'
springCloudVersion = 'Finchley.RELEASE'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-sleuth:2.0.2.RELEASE"
mavenBom 'org.springframework.cloud:spring-cloud-gateway:2.0.2.RELEASE'
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
implementation('org.springframework.cloud:spring-cloud-starter-sleuth')
implementation('org.springframework.cloud:spring-cloud-starter-gateway')
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
and finally the application.yml file for the gateway service...
server:
servlet:
contextPath: /
port: 80
spring:
application:
name: api.gateway.ben.com
sleuth:
trace-id128: true
sampler:
probability: 1.0
cloud:
gateway:
routes:
- id: admin-ui-2
predicates:
- Path=/admin-ui-2/echo/*
filters:
- SetPath=/fred
- AddRequestHeader=X-Request-Foo, 2a9c5e36-2c0f-4ad3-926c-cb20d4428462
- AddResponseHeader=X-Response-Foo, Bar
uri: http://localhost:8080
logging:
pattern:
level: "[%X{X-B3-TraceId}/%X{X-B3-SpanId}] %-5p [%t] %C{2} - %m%n"
level:
org.springframework.web: DEBUG
Spring Cloud Gateway already can log the request and response, you just need to change your log level to TRACE.
logging:
level:
org.springframework: TRACE
or to be more precise to just log req/resp:
logging:
level:
org.springframework.core.codec.StringDecoder: TRACE
Other option is to use a Filter in Spring Cloud Gateway and log req/resp etc with any loggers like Log4j:
routeBuilder.route(id,
r -> {
return r.path(path).and().method(requestmethod).and()
.header(routmap.getRequestheaderkey(), routmap.getRequestheadervalue()).and()
.readBody(String.class, requestBody -> {
return true;
}).filters(f -> {
f.rewritePath(rewritepathregex, replacement);
f.prefixPath(perfixpath);
f.filter(LogFilter);
return f;
}).uri(uri);
});
This filer "LogFilter" you need to define which would have logging logic.

Unable to proceed after clicking a button

I am using spectron to simulate a test on Slack as follows:
1) Open the application.
2) Type in a valid domain.
3) Click Continue.
I am using the following script for that purpose:
JavaScript:
var Application = require('spectron').Application
var assert = require('assert')
var app = new Application({
path: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
})
app.start().then(function() {
// Check if the window is visible
return app.browserWindow.isVisible()
}).then(function(isVisible) {
// Verify the window is visible
assert.equal(isVisible, true)
}).then(function() {
// Get the window's title
return app.client.getTitle()
}).then(function(title) {
// Verify the window's title
// assert.equal(title, 'My App')
}).catch(function(error) {
// Log any failures
console.error('Test failed', error.message)
}).then(function() {
// Stop the application
// return app.stop()
})
console.log('Before setTimeout');
setTimeout(function() {
console.log('Inside setTimeout');
return app.client.waitUntilWindowLoaded()
.windowHandles().then(function(session) {
// Need to return the promise back, if promise is
// it would wait for the state or else app will exit.
console.log('Before switchTab, click & keys');
app.client.switchTab(session.value[1]).click('#domain').keys('testing').click("#submit_team_domain")
.catch(function(error) {
console.error('error message->', error.message);
});
console.log('After switchTab, click & keys');
});
}, 5000);
Java:
Set<String> windowHandles = webDriver.getWindowHandles();
Iterator<String> iterator = windowHandles.iterator();
while (iterator.hasNext()) {
String windowHandle = iterator.next();
if (!windowHandle.equalsIgnoreCase(webDriver.getWindowHandle()))
webDriver.switchTo().window(windowHandle);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
WebElement textBox = webDriver.findElement(By.id("domain"));
textBox.click();
textBox.sendKeys("testing");
WebElement button = webDriver.findElement(By.id("submit_team_domain"));
button = webDriver.findElement(By.id("submit_team_domain"));
button.click();
I am testing with and without a selenium server between a Mac and a Linux machine. The expected result should be that the page should proceed after clicking continue.
What is actually happening is that if a valid domain is provided then it gets stuck and never proceeds. The only exception to this is when we use Spectron to test without a Selenium server on a Linux machine, which itself fails about 20% of the time.
If we provide an invalid domain in any of the cases, it simply proceeds.
Following are the logs when using Spectron without a Selenium Server:
requestOptions -> {"path":"/session/:sessionId/element"}
data -> {"using":"id","value":"domain"}
_ref -> {"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":{"ELEMENT":"0.006260871409230262-1"}},"response":{"statusCode":200,"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":{"ELEMENT":"0.006260871409230262-1"}},"headers":{"content-length":"104","content-type":"application/json; charset=utf-8","connection":"close"},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"127.0.0.1:9515","port":"9515","hostname":"127.0.0.1","hash":null,"search":null,"query":null,"pathname":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element","path":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element","href":"http://127.0.0.1:9515/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element"},"method":"POST","headers":{"Connection":"keep-alive","Accept":"application/json","User-Agent":"webdriverio/webdriverio/4.6.2","Content-Type":"application/json; charset=UTF-8","Content-Length":31}}}}
requestOptions -> {"path":"/session/:sessionId/element/0.006260871409230262-1/click","method":"POST"}
data -> {}
_ref -> {"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"response":{"statusCode":200,"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"headers":{"content-length":"72","content-type":"application/json; charset=utf-8","connection":"close"},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"127.0.0.1:9515","port":"9515","hostname":"127.0.0.1","hash":null,"search":null,"query":null,"pathname":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-1/click","path":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-1/click","href":"http://127.0.0.1:9515/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-1/click"},"method":"POST","headers":{"Connection":"keep-alive","Accept":"application/json","User-Agent":"webdriverio/webdriverio/4.6.2","content-type":"application/json","content-length":2}}}}
WARNING: the "keys" command will be depcrecated soon. Please use a different command in order to avoid failures in your test after updating WebdriverIO.
requestOptions -> {"path":"/session/:sessionId/keys"}
data -> {"value":["c","o","s","m","o","t","e","s","t","i","n","g"]}
_ref -> {"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"response":{"statusCode":200,"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"headers":{"content-length":"72","content-type":"application/json; charset=utf-8","connection":"close"},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"127.0.0.1:9515","port":"9515","hostname":"127.0.0.1","hash":null,"search":null,"query":null,"pathname":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/keys","path":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/keys","href":"http://127.0.0.1:9515/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/keys"},"method":"POST","headers":{"Connection":"keep-alive","Accept":"application/json","User-Agent":"webdriverio/webdriverio/4.6.2","Content-Type":"application/json; charset=UTF-8","Content-Length":59}}}}
requestOptions -> {"path":"/session/:sessionId/element"}
data -> {"using":"id","value":"submit_team_domain"}
_ref -> {"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":{"ELEMENT":"0.006260871409230262-2"}},"response":{"statusCode":200,"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":{"ELEMENT":"0.006260871409230262-2"}},"headers":{"content-length":"104","content-type":"application/json; charset=utf-8","connection":"close"},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"127.0.0.1:9515","port":"9515","hostname":"127.0.0.1","hash":null,"search":null,"query":null,"pathname":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element","path":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element","href":"http://127.0.0.1:9515/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element"},"method":"POST","headers":{"Connection":"keep-alive","Accept":"application/json","User-Agent":"webdriverio/webdriverio/4.6.2","Content-Type":"application/json; charset=UTF-8","Content-Length":43}}}}
requestOptions -> {"path":"/session/:sessionId/element/0.006260871409230262-2/click","method":"POST"}
data -> {}
_ref -> {"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"response":{"statusCode":200,"body":{"sessionId":"bc8bc9abd7a9827258cee72de70d835f","status":0,"value":null},"headers":{"content-length":"72","content-type":"application/json; charset=utf-8","connection":"close"},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"127.0.0.1:9515","port":"9515","hostname":"127.0.0.1","hash":null,"search":null,"query":null,"pathname":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-2/click","path":"/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-2/click","href":"http://127.0.0.1:9515/wd/hub/session/bc8bc9abd7a9827258cee72de70d835f/element/0.006260871409230262-2/click"},"method":"POST","headers":{"Connection":"keep-alive","Accept":"application/json","User-Agent":"webdriverio/webdriverio/4.6.2","content-type":"application/json","content-length":2}}}}
Following are the logs when using Java Language Bindings with a Selenium Server:
15:11:43.884 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:43.884 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element (handler: ServicedSession)
15:11:43.885 INFO - To upstream: {"using":"id","value":"domain"}
15:11:43.885 DEBUG - sun.net.www.MessageHeader#55edb1c210 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 31}
15:11:43.908 DEBUG - sun.net.www.MessageHeader#61b2a16b4 pairs: {null: HTTP/1.1 200 OK}{Content-Length: 102}{Content-Type: application/json; charset=utf-8}{Connection: close}
15:11:43.908 INFO - To downstream: {"sessionId":"976706a5ace12e22ec54d9848f21813c","status":0,"value":{"ELEMENT":"0.8306467617329649-1"}}
15:11:43.917 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:43.917 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-1/click (handler: ServicedSession)
15:11:43.918 INFO - To upstream: {"id":"0.8306467617329649-1"}
15:11:43.919 DEBUG - sun.net.www.MessageHeader#3cbdbdb110 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-1/click HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 29}
15:11:43.986 DEBUG - sun.net.www.MessageHeader#32f13174 pairs: {null: HTTP/1.1 200 OK}{Content-Length: 72}{Content-Type: application/json; charset=utf-8}{Connection: close}
15:11:43.986 INFO - To downstream: {"sessionId":"976706a5ace12e22ec54d9848f21813c","status":0,"value":null}
15:11:43.991 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:43.991 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element (handler: ServicedSession)
15:11:43.992 INFO - To upstream: {"using":"id","value":"domain"}
15:11:43.992 DEBUG - sun.net.www.MessageHeader#3bf4c11110 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 31}
15:11:43.998 DEBUG - sun.net.www.MessageHeader#4f3986fd4 pairs: {null: HTTP/1.1 200 OK}{Content-Length: 102}{Content-Type: application/json; charset=utf-8}{Connection: close}
15:11:43.998 INFO - To downstream: {"sessionId":"976706a5ace12e22ec54d9848f21813c","status":0,"value":{"ELEMENT":"0.8306467617329649-1"}}
15:11:44.002 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:44.002 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-1/value (handler: ServicedSession)
15:11:44.003 INFO - To upstream: {"id":"0.8306467617329649-1","value":["cosmotesting"]}
15:11:44.003 DEBUG - sun.net.www.MessageHeader#1eb8ea9e10 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-1/value HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 54}
15:11:44.032 DEBUG - sun.net.www.MessageHeader#65f4e5f54 pairs: {null: HTTP/1.1 200 OK}{Content-Length: 72}{Content-Type: application/json; charset=utf-8}{Connection: close}
15:11:44.032 INFO - To downstream: {"sessionId":"976706a5ace12e22ec54d9848f21813c","status":0,"value":null}
15:11:44.036 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:44.036 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element (handler: ServicedSession)
15:11:44.036 INFO - To upstream: {"using":"id","value":"submit_team_domain"}
15:11:44.036 DEBUG - sun.net.www.MessageHeader#78955c1310 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 43}
15:11:44.050 DEBUG - sun.net.www.MessageHeader#7b1823924 pairs: {null: HTTP/1.1 200 OK}{Content-Length: 102}{Content-Type: application/json; charset=utf-8}{Connection: close}
15:11:44.050 INFO - To downstream: {"sessionId":"976706a5ace12e22ec54d9848f21813c","status":0,"value":{"ELEMENT":"0.8306467617329649-2"}}
15:11:44.054 INFO - Found handler: org.openqa.selenium.remote.server.ServicedSession#5d54be3a
15:11:44.054 INFO - Handler thread for session 976706a5ace12e22ec54d9848f21813c (chrome): Executing POST on /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-2/click (handler: ServicedSession)
15:11:44.055 INFO - To upstream: {"id":"0.8306467617329649-2"}
15:11:44.055 DEBUG - sun.net.www.MessageHeader#22adda7510 pairs: {POST /session/976706a5ace12e22ec54d9848f21813c/element/0.8306467617329649-2/click HTTP/1.1: null}{User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_151)}{Accept-Encoding: gzip,deflate}{Content-Type: application/json; charset=utf-8}{Connection: close}{Cache-Control: no-cache}{Pragma: no-cache}{Host: localhost:25846}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Content-Length: 29}