Hello im writing an Webapplication using Spring Boot and AngularJs and need an simple file upload which is not working at the moment.
I already read that spring boot should autoconfigure the multipart upload itself when the mvc dependency is present.
From :https://spring.io/guides/gs/uploading-files/
As part of auto-configuring Spring MVC, Spring Boot will create a MultipartConfigElement bean and make itself ready for file uploads.
Javascript Function which sends the Request:
var postFormData = function (file, url, successCallback, errorCallback, progressCallback) {
var xhr = new XMLHttpRequest(),
formData = new FormData();
formData.append("file", file);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
successCallback(xhr.status); //File is Uploaded
} else {
errorCallback(xhr.status);
}
}
};
function progress(evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
progressCallback(percentComplete);
} else {
progressCallback(evt.loaded);
}
}
xhr.open("POST", url, true);
//xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.addEventListener("progress", progress, false);
xhr.send(formData);
}
Multipart configuration Bean in my main app class:
#Bean
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(MAXIMUM_FILE_SIZE);
factory.setMaxRequestSize(MAXIMUM_FILE_SIZE);
return factory.createMultipartConfig();
}
Upload Controller:
#Controller
#RequestMapping("/api")
public class FileUploadController {
#Autowired
UserRepository userRepository;
#Autowired
VolumeMetaRepository volumeMetaRepository;
/**
* Handles File upload of volumedata
*
* #param file Must not be null
**/
#RequestMapping(value = "/volumedata/meta/test", consumes= "multipart/form-data", method=RequestMethod.POST)
#ResponseBody
public void handleFileUpload(
#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
/* try {
InputStream fileStream = file.getInputStream();
OutputStream out = new FileOutputStream(file.getOriginalFilename());
IOUtils.copy(fileStream, out);
} catch (IOException e) {
throw new IllegalStateException("Error uploading file.", e);
}*/
/* String filePath = request.getServletContext().getRealPath("/");
try {
file.transferTo(new File(filePath+ "/" + file.getOriginalFilename()));
} catch (IOException e) {
e.printStackTrace();
}*/
}
}
And because there are other Stackoverflow Threads were a new Spring boot Version solved the issue Im using '1.2.2.BUILD-SNAPSHOT', my gradle dependencies:
version = '1.2.2.BUILD-SNAPSHOT'
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version:version
compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version:version
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version:version
compile group: 'org.springframework.hateoas', name: 'spring-hateoas', version:'0.16.0.RELEASE'
compile group: 'org.springframework.data', name: 'spring-data-rest-webmvc', version:'2.1.4.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version:version
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-rest', version:version
compile group: 'commons-io', name: 'commons-io', version:'2.1'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version:'2.3.4'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-integration', version:version
compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version:version
compile group: 'org.springframework.session', name: 'spring-session', version:'1.0.0.BUILD-SNAPSHOT'
compile group: 'org.springframework.data', name: 'spring-data-redis', version:'1.4.1.RELEASE'
compile group: 'redis.clients', name: 'jedis', version:'2.4.2'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-remote-shell', version:version
compile group:'com.google.guava', name:'guava', version:'18.0'
compile files ('lib/vendor/niftijio.jar');
compile("com.h2database:h2")
testCompile(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version:'1.1.6.RELEASE') {
exclude(module: 'commons-logging')
}
testCompile group: 'com.jayway.jsonpath', name: 'json-path', version:'0.9.1'
testCompile 'org.springframework:spring-test:3.2.3.RELEASE'
testCompile 'junit:junit:4.+'
testCompile "org.mockito:mockito-all:1.9.5"
}
The thrown error is:
{
"timestamp" : "2015-01-19T10:22:53.710Z",
"status" : 400,
"error" : "Bad Request",
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"message" : "Required MultipartFile parameter 'file' is not present",
"path" : "/api/volumedata/meta/test"
}
It tells me that the "file" parameter is not present, but my Request payload shows that the parameter is there.
Request Payload:
------WebKitFormBoundary40qPAhpvA20pd8V1
Content-Disposition: form-data; name="file"
C:\fakepath\test.gz
------WebKitFormBoundary40qPAhpvA20pd8V1--
Has somebody an Idea what is missing in my configuration, or what could cause the error ?
Thanks in advance
I found the reason for the error. It originated not from my Spring controller, but from my angularJS html code.
This was my Upload Input field:
<div class="form-group"
ng-class="{ 'has-error': volume_upload_form.file_field.$invalid && volume_upload_form.file_field.$dirty}">
<label name=file-field-label>Volume Dataset</label>
<input value=file
name=file
ng-model=upload.file
required
id=file
file_ext_validation
type="file"
extensions="nii,NII,gz,jpeg,JPG"/>
<div class="error"
ng-show="volume_upload_form.volume.$invalid">
<small class="error"
ng-show="volume_upload_form.volume.$error.fileExtValidation">
File must be of type ".nii"
</small>
</div>
</div>
As you can see is use the default ng-model too communicate with my angular controller("upload" is my Controller alias).
ng-model=upload.file
But Angular does not support the file input html field by default. So only a string containing the file path was stored in upload.file, NOT an actual File Object. I had to write a custom directive:
var fileModel = function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
});
}
};
}
module.exports = fileModel;
Which returns the actual File object. My new upload html code was as follows:
<div class="form-group"
ng-class="{ 'has-error': volume_upload_form.file_field.$invalid && volume_upload_form.file_field.$dirty}">
<label name=file-field-label>Volume Dataset</label>
<input name=file
file-model="upload.fileModel"
ng-model="file"
required
file_ext_validation
type="file"
extensions="nii,NII,gz,jpeg,JPG"/>
<div class="error"
ng-show="volume_upload_form.volume.$invalid">
<small class="error"
ng-show="volume_upload_form.volume.$error.fileExtValidation">
File must be of type ".nii"
</small>
</div>
</div>
you can see
file-model="upload.fileModel"
links the File Object from the file input field to the angular controller.
Here is the correct Request Payload.
------WebKitFormBoundaryR1AXAwLepSUKJB3i
Content-Disposition: form-data; name="file"; filename="test.nii.gz"
Content-Type: application/x-gzip
------WebKitFormBoundaryR1AXAwLepSUKJB3i--
I met this issue because I named the var incorrect.
In the controller of receiving the file, the RequestParam("file") is expecting a param named like "file".
But I named it as something like "bundle". So it's a bad request.
Related
I'm trying to generate Web API Service from yaml
openapi: 3.0.1
info:
title: ""
description: ''
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.0
servers:
- url: http://localhost:3001
tags:
- name: user
description: Operations with users
- name: stream
description: Operation with streams
paths:
/user/register:
post:
x-vertx-event-bus: user_manager.myapp
tags:
- user
summary: Create user
operationId: createUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserEnter'
required: true
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid phone number
content: {}
x-codegen-request-body-name: body
components:
schemas:
UserEnter:
type: object
properties:
phone:
type: integer
format: int32
password:
type: string
additionalProperties: false
required:
- phone
- password
UserService.kt:
#WebApiServiceGen
interface UserService {
#GenIgnore
companion object{
#JvmStatic
fun create(repository: Repository): UserServiceImpl {
return UserServiceImpl(repository)
}
}
fun createUser(userEnter: UserEnter, request: ServiceRequest, resultHandler: Handler<AsyncResult<ServiceResponse>>)
}
UserEnter.kt:
#DataObject(generateConverter = true, publicConverter = false)
class UserEnter {
var phone: Int = 0
var password: String
constructor(phone: Int, password: String){
this.phone = phone
this.password = password
}
constructor(json: JsonObject): this(
json.getInteger("phone", 0),
json.getString("password", ""),
)
fun toJson(): JsonObject {
return JsonObject.mapFrom(this)
}
}
I'm trying to post the data:
{
"phone": 23423423423,
"password": "enim ut"
}
But the server expects this type of data:
"userEnter": {
{
"phone": 23423423423,
"password": "enim ut"
}
}
The part of generated UserServiceVertxProxyHandler.java:
case "createUser": {
JsonObject contextSerialized = json.getJsonObject("context");
if (contextSerialized == null)
throw new IllegalStateException("Received action " + action + " without ServiceRequest \"context\"");
ServiceRequest context = new ServiceRequest(contextSerialized);
JsonObject params = context.getParams();
try {
service.createUser(
searchOptionalInJson(params, "userEnter").map(j -> (io.vertx.core.json.JsonObject)j).map(j -> new com.md.model.user.UserEnter(j)).orElse(null),
context,
res -> {
if (res.failed()) {
if (res.cause() instanceof ServiceException) {
msg.reply(res.cause());
} else {
msg.reply(new ServiceException(-1, res.cause().getMessage()));
}
} else {
msg.reply(res.result() == null ? null : res.result().toJson());
}
}
);
} catch (Exception e) {
HelperUtils.manageFailure(msg, e, includeDebugInfo);
}
break;
}
Gradle:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.6.21"
kotlin("kapt") version "1.7.0"
application
}
group = "com.md"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
google()
}
dependencies {
kapt("io.vertx:vertx-codegen:4.3.1:processor")
kapt("io.vertx:vertx-web-api-service:4.3.1")
implementation("io.vertx:vertx-core:4.3.1")
implementation("io.vertx:vertx-web:4.3.1")
implementation("io.vertx:vertx-web-validation:4.3.1")
implementation("io.vertx:vertx-web-openapi:4.3.1")
implementation("io.vertx:vertx-service-proxy:4.3.1")
implementation("io.vertx:vertx-web-api-service:4.3.1")
compileOnly("io.vertx:vertx-codegen:4.3.1")
implementation("io.vertx:vertx-mongo-client:4.3.1")
implementation("org.slf4j:jcl-over-slf4j:1.7.36")
implementation("ch.qos.logback:logback-classic:1.2.11")
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClass.set("MainKt")
}
What I’m doing wrong? I’ve made all like in this example: https://github.com/vert-x3/vertx-examples/tree/4.x/web-api-service-example/src/main/java/io/vertx/examples/webapiservice
My YAML file is correct, I’ve generated the kotlin-client on https://editor.swagger.io and it sends:
{
"phone": 23423423423,
"password": "enim ut"
}
I'm working with Laravel 8 + inertiajs. I can create a product with or without an image. But when I try to update a product and upload a new image, the validation looks for the required field even they're already filled.
here is my input field:
<input name="images" type="file" #input="form.images = $event.target.files[0]" />
in my vue:
props: {
product: Object,
categories: Array
},
data() {
return {
form: this.$inertia.form({
name: this.product.name,
category_id: this.product.category_id,
description: this.product.description,
date: this.product.date,
images: this.product.images
})
}
},
methods: {
update() {
this.form.put(this.route('products.update', this.product.id, {
preserveState: true
}))
},
}
})
my update controller:
public function update(UpdateProductRequest $request, Product $product)
{
$inputs = $request->validated();
if ($request->hasFile('images')) {
$filename = $request->images->getClientOriginalName();
$file = $request->images->storeAs(('images'), $filename);
$product->images = $file;
$inputs['images'] = $product->images;
}
$product->name = $inputs['name'];
$product->category_id = $inputs['category_id'];
$product->description = $inputs['description'];
$product->date = $inputs['date'];
$product->update();
session()->flash('flash.banner', 'Product Updated Successfuly');
session()->flash('flash.bannerStyle', 'success');
return redirect()->route('products.index');
}
multipart/form-data request is not natively supported in some languages for the put,patch or delete methods. The workaround here is to simply upload files using post instead.
Some frameworks, such as Laravel and Rails, support form method spoofing, which allows you to upload the files using post, but have the framework handle the request as a put or patch request. This is done by including a _method attribute in the data of your request.
Inertia.post(`/users/${user.id}`, {
_method: 'put',
avatar: form.avatar,
})
I am building a selenium automation project using cucumber and kotlin, but after I have set up the skeleton. The steps can't be identified by cucumber. I mainly call gradle cucumber on terminal to begin the test.
build.gradle
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
id "com.github.spacialcircumstances.gradle-cucumber-reporting" version "0.1.7"
}
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
jcenter()
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
testImplementation 'io.cucumber:cucumber-java8:4.7.1'
testImplementation 'io.cucumber:cucumber-junit:4.3.1'
implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
testImplementation 'no.tornado:tornadofx:1.7.17'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
configurations {
cucumberRuntime {
extendsFrom testImplementation
}
}
task cucumber() {
dependsOn assemble, compileTestJava
doLast {
javaexec {
main = "cucumber.api.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.test.output
args = ['--plugin', 'json:test_reports/' + getDate() + '_automation_test_result.json', '--glue', 'src/test/kotlin/steps', 'src/test/resources/feature']
}
}
}
cucumberReports {
outputDir = file('test_reports/' + getDate()+ '_html')
buildId = '0'
reports = files('test_reports/' + getDate() + '_automation_test_result.json', 'test_reports/cucumber-json.json')
}
static def getDate() {
def date = new Date()
def formattedDate = date.format('yyyy-MM-dd')
return formattedDate
}
Below is the structure:
This is the folders
Below is the result:
Task :cucumber
Sep 18, 2019 5:27:40 PM cucumber.api.cli.Main run
WARNING: You are using deprecated Main class. Please use io.cucumber.core.cli.Main
Undefined scenarios:
src/test/resources/feature/test.feature:15 # Input something in the search bar
src/test/resources/feature/test.feature:16 # Input something in the search bar
src/test/resources/feature/test.feature:25 # Change to different sub-page
src/test/resources/feature/test.feature:26 # Change to different sub-page
src/test/resources/feature/test.feature:27 # Change to different sub-page
src/test/resources/feature/test.feature:28 # Change to different sub-page
6 Scenarios (6 undefined)
30 Steps (30 undefined)
0m0.100s
You can implement missing steps with the snippets below:
Given("I have logged in as a super admin", () -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
Given("I have navigated to CRM", () -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
Given("I have clicked the client button on the navigation bar", () -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
When("I input {string} in the search bar", (String string) -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
Then("the search result should contain {string}", (String string) -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
Given("I have navigated to the home page on CRM", () -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
When("I click the {string} on the navigation bar", (String string) -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
Then("I should be brought to different page", () -> {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
});
BUILD SUCCESSFUL in 2s
4 actionable tasks: 1 executed, 3 up-to-date
I can navigate to the step file when I ctrl+click on the implemented Given or When steps. It seems it can't find the steps definition when it runs. What am i doing wrong here?
I had this exact issue and the answer above is correct, but a bit unclear. (I removed everything else rather than leaving the resources folder in there too!)
This is the task cucumber() section of my build.gradle file:
task cucumber() {
dependsOn assemble, compileTestKotlin
doLast {
javaexec {
main = "io.cucumber.core.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
args = ['--plugin', 'pretty', '--glue', 'hellocucumber', 'src/test/resources']
}
}
}
where hellocucumber is my package name, as outlined in this example.
I hope it helps!
I'm absolutely new to Kotlin. I'm trying to make a simple object on the backend side by Kotlin and get it on frontend Vuejs. How can I do something like this (this is the raw code of HeaderBar.kt, all my attempts were denied by compiler):
object HeaderBar {
val computed = object {
fun items(): Array<Item> {
items.add(Item(
"NY",
"Bill"
))
return items
}
}
data class Item(
val city: String,
val name: String
)
}
on Kotlin side?
And get the items on HeaderBar.vue. I'm not sure, but I do this by:
<template>
<div class="main-header">
<div v-for="item in items" class="items">
<span class="city">{{item.city}}</span>
<span class="name">{{item.name}}</span>
</div>
</div>
<template>
<script>
export default path.to.HeaderBar
</script>
First off all it's not simple question. Kotlin/Js not so mature as Kotlin/Jvm so there are many not so simple tasks.
First you need to somehow compile to javascript and then you need to attach Vue to kotlin/javascript code.
Webpack can make it easer, so I write a simple example to show you how to write your example in Kotlin.
!Warning!: all code below is just draft (and has been writen only in demonstration purpose), so use it in your projects with special caution!
Lets create project with below structure:
Application.kt:
package vue_test
fun VueJs(init: VueContext.() -> Unit) = Vue(VueContext().apply(init))
class VueContext {
var el: String = ""
var data: dynamic = js("{}")
}
fun main(args: Array<String>) {
val app: dynamic = VueJs {
el = "#app"
data = mapOf("items" to listOf(
Item("NY", "Bill"),
Item("Test", "Test2")
)).toJs()
}
}
data class Item(
val city: String,
val name: String
)
fun Map<String, Any>.toJs(): dynamic {
val result: dynamic = object {}
for ((key, value) in this) {
when (value) {
is String -> result[key] = value
is List<*> -> result[key] = (value as List<Any>).toJs()
else -> throw RuntimeException("value has invalid type")
}
}
return result
}
fun List<Any>.toJs(): dynamic {
val result: dynamic = js("[]")
for (value in this) {
when (value) {
is String -> result.push(value)
is Item -> {
result.push(value.toJs())
}
else -> throw RuntimeException("value has invalid type")
}
}
return result
}
fun Item.toJs(): dynamic {
val result: dynamic = object {}
result["city"] = this.city
result["name"] = this.name
return result
}
I have write few function toJs which converts Kotlin object to Js object. It theory you may use JSON serialization to simplify this, or other more simple solution (if exists).
Vue.kt
#file:JsModule("vue")
package vue_test
#JsName("default")
external open class Vue(init: dynamic)
In this file we have only Vue declarations.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Test project</title>
</head>
<body class="testApp">
<h1>Kotlin-Js test</h1>
<div id="app">
<div class="main-header">
<div v-for="item in items" class="items">
<span class="city">{{item.city}}</span>
<span class="name">{{item.name}}</span>
</div>
</div>
</div>
<script type="text/javascript" language="JavaScript" src="frontend.bundle.js"></script>
</body>
</html>
Buldle has been created by webpack, and I has put this script to bottom because Vue needed to start his manipulations only then all necessary html tags has been already exists.
My build.gradle file with kotlin-frontend plugin and kotlin-js plugin:
buildscript {
ext.kotlin_version = '1.2.10'
repositories {
mavenCentral()
jcenter()
maven {
url "https://dl.bintray.com/kotlin/kotlin-eap"
}
maven {
url "https://repo.gradle.org/gradle/libs-releases-local"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.21"
}
}
group 'test'
version '1.0-SNAPSHOT'
apply plugin: 'kotlin-platform-js'
apply plugin: 'org.jetbrains.kotlin.frontend'
repositories {
mavenCentral()
}
kotlinFrontend {
sourceMaps = true
npm {
dependency("vue")
}
webpackBundle {
port = 8080
bundleName = "frontend"
contentPath = file('src/main/web')
webpackConfigFile = project.projectDir.path + '/webpack.config.js'
}
}
compileKotlin2Js {
kotlinOptions.metaInfo = true
kotlinOptions.outputFile = "$project.buildDir.path/js/${project.name}.js"
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'commonjs'
kotlinOptions.main = "call"
}
kotlin {
experimental {
coroutines 'enable'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
settings.gradle
rootProject.name = 'test-kotlin-vue'
and last file, custom webpack configuration:
var config = require('./build/WebPackHelper.js')
var path = require('path')
module.exports = {
entry: config.moduleName,
output: {
path: path.resolve('./bundle'),
publicPath: '/build/',
filename: 'frontend.bundle.js'
},
module: {
rules: []
},
resolve: {
modules: [path.resolve('js'), path.resolve('..', 'src'), path.resolve('.'), path.resolve('node_modules')],
extensions: ['.js', '.css'],
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
}
},
devtool: '#source-map'
};
console.log(module.exports.resolve.modules);
With kotlin-frontend plugin you could not use separate webpack config, but in this example Vue needed a full version to compile template, so it's needed to add alias in webpack. And I don't know how to do this in build.gradle.
Hope this will help you!
To start project with dev bundle run this command: gradle build webpack-run, and then open http://localhost:8080 in your browser
To stop test run command: gradle webpack-stop
I'm new to Java and Kotlin, trying to build a contact form with Ktor, so I enabled the insecure connection of my gmail from here, and built the app below:
blogApp.kt:
package blog
import org.jetbrains.ktor.netty.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.features.*
import org.jetbrains.ktor.host.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.response.*
import org.apache.commons.mail.*
fun Application.module() {
install(DefaultHeaders)
install(CallLogging)
install(Routing) {
get("/") {
call.respondText("""
My Example Blog2
<form action="/contact-us" method="post">
<input name="subject" placeholder="Subject">
<br>
<textarea name="message" placeholder="Your message ..."></textarea>
<br>
<button>Submit</button>
</form>
""", ContentType.Text.Html)
}
post("/contact-us") {
SimpleEmail().apply {
setHostName("smtp.gmail.com")
setSmtpPort(465)
setAuthenticator(DefaultAuthenticator("my_alias#gmail.com", "my_gmil_passoword"))
setSSLOnConnect(true)
setFrom("my_alias#gmail.com")
setSubject("subject") // I need to use formParam
setMsg("message") // I need to use formParam
addTo("my_alias#gmail.com")
}.send() // will throw email-exception if something is wrong
call.respondRedirect("/contact-us/success")
}
get("/contact-us/success") {
call.respondText("Your message was sent", ContentType.Text.Html)
}
}
}
fun main(args: Array<String>) {
embeddedServer(Netty, 8080, watchPaths = listOf("BlogAppKt"), module = Application::module).start()
}
build.gradle:
group 'Example'
version 'alpha'
buildscript {
ext.kotlin_version = '1.1.4-3'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
sourceCompatibility = 1.8
ext.ktor_version = '0.4.0'
repositories {
mavenCentral()
maven { url "http://dl.bintray.com/kotlin/ktor" }
maven { url "https://dl.bintray.com/kotlin/kotlinx" }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.ktor:ktor-core:$ktor_version"
compile "org.jetbrains.ktor:ktor-netty:$ktor_version"
compile "org.apache.commons:commons-email:1.4"
compile "org.slf4j:slf4j-simple:1.7.25"
compile "ch.qos.logback:logback-classic:1.2.1"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
kotlin {
experimental {
coroutines "enable"
}
}
jar {
baseName 'abc'
manifest {
attributes 'Main-Class': 'blog.BlogAppKt'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
and things gone smoothly with it, I was able to send an email to myself then redirect to the success page, but the message got sent is with pre-set data:
setSubject("subject") // I need to use formParam
setMsg("message") // I need to use formParam
how can I make the Ktor receive the data the user really entered in the form, how can I read the form params?
You can use call.receive<ValuesMap>() and introspect the data:
import org.jetbrains.ktor.request.* // for recieve
import org.jetbrains.ktor.util.* // for ValuesMap
post("/contact-us") {
val post = call.receive<ValuesMap>()
val subj = post["subject"]
val msg = post["message"]
SimpleEmail().apply { ... }
}
NOTE: ValuesMap is deprecated in latest ktor version, so use the following code:
val post = call.receiveParameters()
Well although the above answer would have been correct at that point of time, today when I am going through kotlin + Ktor, I see that the above answer is not valid anymore.
What you need now is something like this:
call.receive< Parameters >()["PARAM_NAME"]
Parameters class is in the following package: io.ktor.http.Parameters
Nikhil