Can't serve dynamic Web Content with Spring Boot and Kotlin - spring

Based on a Spring Boot tutorial to served dynamic web content, I wanted to do the same in Kotlin. My Kotlin project is based on this tutorial. I've no issue running the code of both tutorials.
From my understanding, I only needed to add a controller that would return a reference to a template.
Here HelloController.kt (located under src/main/kotlin/foo/controller) :
package foo.controller
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
#Controller
class HelloController {
private val log = LoggerFactory.getLogger(HelloController::class.java)
#RequestMapping("/")
fun hello(): String {
log.info("foo")
return "index"
}
}
Here is the simple "template", index.html that I want to access (located under src/main/resources/templates/index.html):
<html>
<head>
</head>
<body>
Bar
</body>
</html>
So technically, if I go to localhost:8080 I should have index.html displayed which I don't. Instead I have a 404 error. I do have the logged displayed so the hellomethod is called. What am I doing wrong? I didn't see any configuration file in the Spring Boot tutorial so I guess Spring is doing something under the hood to get the correct ressource from what a function returns.
EDIT:
Has requested my graddle imports:
buildscript {
val springBootVersion = "1.4.3.RELEASE"
val kotlinVersion = "1.0.6"
extra["kotlinVersion"] = kotlinVersion
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion")
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
}
apply {
plugin("kotlin")
plugin("kotlin-spring")
plugin("kotlin-jpa")
plugin("org.springframework.boot")
}
version = "0.0.1-SNAPSHOT"
configure<JavaPluginConvention> {
setSourceCompatibility(1.8)
setTargetCompatibility(1.8)
}
repositories {
mavenCentral()
}
val kotlinVersion = extra["kotlinVersion"] as String
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("com.h2database:h2")
compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compile("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
compile("org.apache.commons:commons-io:1.3.2")
compile("org.apache.commons:commons-lang3:3.3.1")
testCompile("org.springframework.boot:spring-boot-starter-test")
}

I looks like spring-boot-starter-web dependency is not enough to setup view resolving. Try to add spring-boot-starter-thymeleaf dependency and Thymeleaf should handle your html file.
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
Your HTML files should be in src/main/resources/templates or they might not be automatically detected.

Related

"Whitelabel Error Page" when using SpringDoc with Kotlin and WebFlux

I am getting Whitelabel Error Page when using the SpringBoot + Kotlin + SpringDoc + WebFlux. No documentation is generated despite dependencies from offical springdoc documentation page
enter image description here.
application.yaml does not contain any properties specified
private object Version {
const val kotlinCoroutinesVersion = "1.6.4"
const val openApiVersion = "1.6.12"
}
plugins {
val kotlinVersion = "1.8.10"
id("org.springframework.boot") version "3.0.2"
id("io.spring.dependency-management") version "1.1.0"
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
}
dependencies {
// Coroutines dependencies
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.kotlinCoroutinesVersion}")
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${Version.kotlinCoroutinesVersion}")
// Spring dependencies
implementation("org.springframework.boot:spring-boot-starter-webflux")
// Swagger
implementation("org.springdoc:springdoc-openapi-webflux-ui:${Version.openApiVersion}")
implementation("org.springdoc:springdoc-openapi-kotlin:${Version.openApiVersion}")
// Validation
implementation("javax.validation:validation-api:2.0.1.Final")
}
Example Controller looks like:
#RestController
#RequestMapping("/product")
internal class OrderController(
private val orderCommand: OrderCommand
) {
#PostMapping
suspend fun saveProduct(#Valid #RequestBody createOrderRequest: CreateOrderRequest): ResponseEntity<OrderCreatedResponse> {
// Some code
}
Main function:
#EnableWebFlux
#SpringBootApplication
class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
Any ideas what can I missed? I checked locally on localhost:8080/v3/api-docs.yaml and localhost:8080/swagger-ui.html but still no page has been generated
Adding
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-graphql")
to dependencies solved the issue

Can't call AppEngine Image API getServingUrl method when using SpringBoot stack

Our project needs to serve scaled images. AppEngine ImageApi has a wonderful method called getServingUrl. But this API is only accessible from AppEngine.
Problem
I've created a PoC project with SpringBoot and gradle for AppEngine. And whenever I call a simple endpoint that should call getServingUrl I get the following error:
Can’t make API call blobstore.CreateEncodedGoogleStorageKey in a thread that is neither the original request thread nor a thread created by ThreadManage
But when I do a similar setup that is based on Servlets, it works as expected.
SpringBoot Setup
Here is an example of some files from my setup.
build.gradle:
;; https://github.com/GoogleCloudPlatform/app-gradle-plugin
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.2.0'
}
}
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'com.google.cloud.tools.appengine' // App Engine tasks
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "com.google.appengine:appengine-api-1.0-sdk:2.0.4"
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
appengine { // App Engine tasks configuration
deploy { // deploy configuration
projectId = 'GCLOUD_CONFIG' // delegate to project in gcloud config
version = 'GCLOUD_CONFIG' // delegate to gcloud to generate a version
}
}
appengine/app.yml
runtime: java11
Controller
package com.example.demo.controller;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.ServingUrlOptions;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ImageController {
#RequestMapping("/image-url")
public ImageUrlDto hello(#RequestParam String bucket, #RequestParam String image) {
ServingUrlOptions options = ServingUrlOptions.Builder
.withGoogleStorageFileName(String.format("/gs/%s/%s", bucket, image));
return new ImageUrlDto(ImagesServiceFactory.getImagesService().getServingUrl(options));
}
}
class ImageUrlDto {
String url;
public ImageUrlDto(String url) {
this.url = url;
}
}
Question
Servlet setup is working correctly, but spring setup no. I've read that Image API is accessible only within AppEngine standart environemnt and request thread/ThreadManager spawned thread.
Am I missing something for that Spring setup? Probably that requires some another form of deployment or thread configuration in order to work with Image API?

NoSuchMethodException QueryDSL with Spring Boot & Spring Data Mongo

I am trying to implement Query DSL on my Spring Boot 2.0.4.RELEASE app that uses Spring Data Mongo 2.0.4.RELEASE & Gradle 4.10.
I am using Spring Tool Suite for running it locally.
Did the following steps which I found from multiple sources including Spring data documentation:
created gradle/querydsl.gradle which has below content to generate Q classes
apply plugin: "com.ewerk.gradle.plugins.querydsl"
sourceSets {
main {
java {
srcDir "$buildDir/generated/source/apt/main"
}
}
}
querydsl {
springDataMongo = true
querydslSourcesDir = "$buildDir/generated/source/apt/main"
}
dependencies {
compile "com.querydsl:querydsl-mongodb:4.1.4"
compileOnly "com.querydsl:querydsl-apt:4.1.4"
}
sourceSets.main.java.srcDirs = ['src/main/java']
Calling above gradle file from main build.gradle as shown below
buildscript {
ext { springBootVersion = "2.0.4.RELEASE" }
repositories { mavenCentral() }
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.9"
}
}
plugins {
id "java"
id "eclipse"
id "org.springframework.boot" version "2.0.4.RELEASE"
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
sourceCompatibility = 1.8
repositories { mavenCentral() }
dependencies {
...
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-mongodb:${springBootVersion}")
...
}
apply from: 'gradle/querydsl.gradle'
/* Added this because Eclipse was not able to find generated classes */
sourceSets.main.java.srcDirs = ['build/generated/source/apt/main','src/main/java']
compileJava.dependsOn processResources
processResources.dependsOn cleanResources
After this updated the Repository annotated interface as below. Note: I also use Fragment Repository FragmentOrderRepository for some custom queries.
public interface OrderRepository<D extends OrderDAO>
extends EntityRepository<D>, PagingAndSortingRepository<D, String>, FragmentOrderRepository<D>, QuerydslPredicateExecutor<D> {}
Then in controller created a GET mapping as shown here
#RestController
public class OrderController {
#GetMapping(value="/orders/dsl", produces = { "application/json" })
public ResponseEntity<List> getOrdersDSL(#QuerydslPredicate(root = OrderDAO.class) Predicate predicate, Pageable pageable, #RequestParam final MultiValueMap<String, String> parameters) {
return (ResponseEntity<List>) orderService.getTools().getRepository().findAll(predicate, pageable);
}
}
Then in my runner class I added EnableSpringDataWebSupport annotation
#SpringBootApplication
#EnableSpringDataWebSupport
public class SampleApp {
public static void main(String[] args) {
SpringApplication.run(SampleApp.class, args);
}
}
With this my app starts up without any errors but when I try hitting the path http://localhost:5057/orders/dsl?email=test#test.com
I get a NoSuchMethodException with message No primary or default constructor found for interface com.querydsl.core.types.Predicate.
Can anyone please help with some pointers to solve this issue?
It seems that parameters are not getting resolved to a type.
---- UPDATE 09/19/19 ----
While debugging I found that a class HandlerMethodArgumentResolverComposite which finds ArgumentResolver for given MethodParameter from a List of argumentResolvers(of type HandlerMethodArgumentResolver). This list does not contain QuerydslPredicateArgumentResolver. Hence it is not able to resolve the arguments.
This means QuerydslWebConfiguration which adds above resolver for Predicate type is not getting called, which in turn indicates that some AutoConfiguration is not happening.
Probably I am missing some annotation here.
Found the mistake I was doing, was missing EnableWebMvc annotation on my Configuration annotated class.
Details are in this documentation.

Gatling is not working with SpringBoot 2.0.1.RELEASE

I'm trying to setup Gatling with Spring Boot 2.0.1.RELEASE.
I've created this api:
#RestController
#RequestMapping("/contact")
public class ContactController {
#GetMapping
public ResponseEntity get() {
return ResponseEntity.ok("Contact retrieved successfully");
}
}
And this basic simulation:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class ContactSimulation extends Simulation {
val httpConf = http.baseURL("http://localhost:8080")
val scn = scenario("GetContact")
.exec(
http("GetContact")
.get("/contact")
.check(status.is(200))
)
setUp(scn.inject(atOnceUsers(1))).protocols(httpConf)
}
The build.gradle is configured this way:
buildscript {
ext {
springBootVersion = '2.0.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'scala'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.gatling.poc'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.gatling.highcharts:gatling-charts-highcharts:2.3.0')
}
task loadTest(type: JavaExec) {
dependsOn testClasses
description = "Load Test With Gatling"
group = "Load Test"
classpath = sourceSets.test.runtimeClasspath
jvmArgs = [
"-Dgatling.core.directory.binaries=${sourceSets.test.output.classesDir.toString()}"
]
main = "io.gatling.app.Gatling"
args = [
"--simulation", "com.gatling.poc.simulations.ContactSimulation",
"--results-folder", "${buildDir}/gatling-results",
"--binaries-folder", sourceSets.test.output.classesDir.toString(),
"--bodies-folder", sourceSets.test.resources.srcDirs.toList().first().toString() + "/gatling/bodies",
]
}
While executing the tests:
./gradlew loadTest
I'm getting the following stacktrace:
21:00:12.804 [main] WARN io.netty.util.concurrent.DefaultPromise - An exception was thrown by org.asynchttpclient.netty.request.NettyRequestSender$1.operationComplete()
java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId;
at io.netty.channel.AbstractChannel.newId(AbstractChannel.java:111)
at io.netty.channel.AbstractChannel.<init>(AbstractChannel.java:83)
at io.netty.bootstrap.FailedChannel.<init>(FailedChannel.java:33)
at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:330)
at io.netty.bootstrap.Bootstrap.doResolveAndConnect(Bootstrap.java:163)
at io.netty.bootstrap.Bootstrap.connect(Bootstrap.java:156)
at org.asynchttpclient.netty.request.NettyChannelConnector.connect0(NettyChannelConnector.java:81)
at org.asynchttpclient.netty.request.NettyChannelConnector.connect(NettyChannelConnector.java:69)
at org.asynchttpclient.netty.request.NettyRequestSender$1.onSuccess(NettyRequestSender.java:292)
at org.asynchttpclient.netty.request.NettyRequestSender$1.onSuccess(NettyRequestSender.java:285)
at org.asynchttpclient.netty.SimpleFutureListener.operationComplete(SimpleFutureListener.java:24)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:485)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:162)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:33)
at org.asynchttpclient.netty.request.NettyRequestSender.sendRequestWithNewChannel(NettyRequestSender.java:285)
at org.asynchttpclient.netty.request.NettyRequestSender.sendRequestWithCertainForceConnect(NettyRequestSender.java:136)
at org.asynchttpclient.netty.request.NettyRequestSender.sendRequest(NettyRequestSender.java:107)
at org.asynchttpclient.DefaultAsyncHttpClient.execute(DefaultAsyncHttpClient.java:216)
at org.asynchttpclient.DefaultAsyncHttpClient.executeRequest(DefaultAsyncHttpClient.java:184)
at org.asynchttpclient.DefaultAsyncHttpClient.executeRequest(DefaultAsyncHttpClient.java:206)
at io.gatling.http.ahc.HttpEngine.warmpUp(HttpEngine.scala:96)
at io.gatling.http.protocol.HttpProtocol$$anon$1.$anonfun$newComponents$1(HttpProtocol.scala:62)
at io.gatling.core.protocol.ProtocolComponentsRegistry.comps$1(Protocol.scala:67)
at io.gatling.core.protocol.ProtocolComponentsRegistry.$anonfun$components$4(Protocol.scala:69)
at scala.collection.mutable.HashMap.getOrElseUpdate(HashMap.scala:82)
at io.gatling.core.protocol.ProtocolComponentsRegistry.components(Protocol.scala:69)
at io.gatling.http.action.HttpActionBuilder.lookUpHttpComponents(HttpActionBuilder.scala:25)
at io.gatling.http.action.sync.HttpRequestActionBuilder.build(HttpRequestActionBuilder.scala:33)
at io.gatling.core.structure.StructureBuilder.$anonfun$build$1(StructureBuilder.scala:34)
at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:122)
at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:118)
at scala.collection.immutable.List.foldLeft(List.scala:86)
at io.gatling.core.structure.StructureBuilder.build(StructureBuilder.scala:33)
at io.gatling.core.structure.StructureBuilder.build$(StructureBuilder.scala:32)
at io.gatling.core.structure.ScenarioBuilder.build(ScenarioBuilder.scala:38)
at io.gatling.core.structure.PopulationBuilder.build(ScenarioBuilder.scala:98)
at io.gatling.core.scenario.SimulationParams.$anonfun$scenarios$1(Simulation.scala:188)
at scala.collection.immutable.List.map(List.scala:283)
at io.gatling.core.scenario.SimulationParams.scenarios(Simulation.scala:188)
at io.gatling.app.Runner.run0(Runner.scala:95)
at io.gatling.app.Runner.run(Runner.scala:64)
at io.gatling.app.Gatling$.start(Gatling.scala:59)
at io.gatling.app.Gatling$.fromArgs(Gatling.scala:43)
at io.gatling.app.Gatling$.main(Gatling.scala:35)
at io.gatling.app.Gatling.main(Gatling.scala)
If I create the same project using SpringBoot 1.5.12.RELEASE, it works perfectly. I'm suspecting of the spring-boot-gradle-plugin since it changes according to the SpringBoot version.
If necessary I can create a project into github to demonstrate the problem. =)
I would appreciate if someone could help me!
I got fix this issue.
The problem was the version of the io.netty group used by the SpringBoot 2.0.1.RELEASE. It is using the version 4.1.23.Final which for some reason (maybe some change in scala) is not executing the gatling tests.
I did the downgrade to use the same version as SpringBoot 1.5.12.RELEASE, which is 4.0.51.Final. I had to add this configuration to the build.gradle:
{
// This build.gradle is the same that I posted above. For the sake of simplicity I just put the configuration that has been necessary to solve the problem.
// After dependencies{} section, I had to put this:
configurations.all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'io.netty') {
details.useVersion "4.0.51.Final"
}
}
}
}
}
I don't think is the best solution since I'm reverting the io.netty version to use a older version, which can have some side effect. In my case I didn't have the option of downgrading the SpringBoot version. If you have the option to revert the SpringBoot version, I would recommend to do that.

Spring boot executable jar can not resolve freemarker template

I am learning web application building with spring boot and java. I've got my app working when I run it through Spring Tool Suite but after I build executable jar using bootRepackage and run it, It's not able to resolve the freemarker views.
I am not sure what's wrong. Any help would be appreciated.
Following is my application.properties related to freemarker,
spring.http.encoding.charset=UTF-8
spring.freemarker.cache=false
spring.freemarker.charset=utf-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.template-loader-path=classpath:/templates/,classpath:/templates/web/
My jar structure,
BOOT-INF
classes
com
scss
static
templates
web
story.html
app
application.properties
log4j2.xml
META-INF
org
my controller,
#Controller
public class HomeController {
#Autowired
private AppLog appLogger;
#RequestMapping("/")
public ModelAndView Index(HttpServletRequest request) {
appLogger.log(Level.ERROR,AppLogSource.Web, "Reached Controller", null);
String testAttribute = request.getAttribute("com.demo.test").toString();
Map<String, String> vm = new HashMap<String, String>();
vm.put("testAttribute", testAttribute);
return new ModelAndView("/web/story", vm);
}
}
I verified that I am hitting the log step so I think issue is in resolving the view but I could be wrong and missing something else. So let me know if you need more info.
Thanks again!
Best,
Mrunal
edit
Gradle File,
buildscript {
ext {
springBootVersion = '1.4.1.RELEASE'
}
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("com.moowork.gradle:gradle-node-plugin:1.2.0")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
apply plugin: 'com.moowork.node'
apply plugin: 'com.moowork.grunt'
jar {
baseName = 'testDemo'
version = '0.0.1'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
node {
version = '6.11.3'
npmVersion = '3.10.10'
download = true
}
task gruntMinifyJs(type: GruntTask){
args=['minifyJs', '--debug']
}
task gruntMinifyCss(type: GruntTask){
args=['minifyCss', '--debug']
}
task buildFrontEnd(type: GruntTask) {
args = ['default', '--debug']
}
npmInstall.dependsOn(nodeSetup)
buildFrontEnd.dependsOn(npmInstall)
gruntMinifyCss.dependsOn(npmInstall)
gruntMinifyJs.dependsOn(npmInstall)
build.dependsOn(buildFrontEnd)
configurations {
all*.exclude group: 'ch.qos.logback', module:'logback-classic'
all*.exclude group: 'ch.qos.logback', module:'logback-core'
}
dependencies {
compile('org.springframework.boot:spring-boot-devtools')
compile('org.springframework.boot:spring-boot-starter-freemarker')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter:1.4.1.RELEASE'){
exclude group:'org.springframework.boot', module:'spring-boot-starter-logging'
}
compile('org.springframework.boot:spring-boot-starter-jdbc'){
exclude group:'org.apache.tomcat', module:'tomcat-jdbc'
}
compile('mysql:mysql-connector-java')
compile('com.zaxxer:HikariCP-java6:2.3.13')
compile('org.springframework.boot:spring-boot-starter-log4j2:1.4.1.RELEASE')
compile('com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.8')
compile('com.google.zxing:core:3.3.0')
compile('org.antlr:antlr4-runtime:4.5')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
edit 3,
further updates,
So I attached remote debugger and I found that spring is using ContentNegotiatingViewResolver to resolve the view as InternalResourceView but when I execute through spring tool suite it resolves properly to FreemarkerView.
I hope this helps someone to narrow down my issue. I'll see if I can get anywhere else in mean time by stepping through debugger.
Perhaps the freemarker jar file is not specified as a dependency in your spring boot application module. Unsure if you are running maven or gradle but do make sure you have the freemarker library included in your build.
See downloads # https://mvnrepository.com/artifact/org.freemarker/freemarker

Resources