Issue
The Ktor application's main method is not called when deployed to AppEngine. In the applications's main method is logic to retrieve content from an API request based on a Timer and save that information to a Firestore database which a client consumes.
This logic currently works when deployed in a Jar to AppEngine. However, implementing this with Ktor would save deploy time and help future proof the backend service if endpoints are required.
Expected
The Ktor application's main method is called once the app is deployed to AppEngine similar to how an application's main method is called when ran in IntelliJ.
Actual
The main method is only called once the app's hosted route is called.
ie: https://[yourProjectName].appspot.com
Setup
Main Method
import io.ktor.application.Application
fun Application.main() {
// App logic here.
}
build.gradle
buildscript {
ext.kotlin_version = '1.3.10'
ext.ktor_version = '1.0.0'
ext.appengine_version = '1.9.60'
ext.appengine_plugin_version = '1.3.4'
ext.junitJupiterVersion = '5.0.3'
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.cloud.tools:appengine-gradle-plugin:$appengine_plugin_version"
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.3'
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.appengine'
sourceSets {
main.kotlin.srcDirs = [ 'src/main/kotlin' ]
}
sourceCompatibility = 1.8
repositories {
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/ktor" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "io.ktor:ktor-server-servlet:$ktor_version"
implementation "io.ktor:ktor-html-builder:$ktor_version"
providedCompile "com.google.appengine:appengine:$appengine_version"
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.1'
implementation 'com.google.firebase:firebase-admin:6.3.0'
implementation 'com.google.apis:google-api-services-youtube:v3-rev204-1.23.0'
testCompile group: 'junit', name: 'junit', version: '4.12'
// JUnit Jupiter API and TestEngine implementation
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
testCompile("org.assertj:assertj-core:3.10.0")
// To avoid compiler warnings about #API annotations in JUnit code
testCompileOnly('org.apiguardian:apiguardian-api:1.0.0')
}
kotlin.experimental.coroutines = 'enable'
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
task run(dependsOn: appengineRun)
appengine {
deploy {
version = 'media-staging-1201181257pm'
}
}
src/main/resources/application.conf
ktor {
application {
modules = [ InitializationKt.main ]
}
}
src/main/webapp/WEB-INF/
appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<threadsafe>true</threadsafe>
<runtime>java8</runtime>
</appengine-web-app>
web.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<servlet>
<display-name>KtorServlet</display-name>
<servlet-name>KtorServlet</servlet-name>
<servlet-class>io.ktor.server.servlet.ServletApplicationEngine</servlet-class>
<!-- path to application.conf file, required -->
<init-param>
<param-name>io.ktor.config</param-name>
<param-value>application.conf</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>KtorServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Thank you to Gabriel Machado on Kotlin Slack for answering this.
Gabriel recommends using a Cron Job as opposed to a Timer Task because there may be issues with a Timer thread based on scaling type.
GAE probably expects your app to look like a Java app (e.g., a Java style "main", not a Kotlin style "main").
Look at this for more info on how to do this: How to run Kotlin class from the command line?
Related
Problem you have encountered:
- after deploy app to appengine on gcloud with gradle default web page is index.html. BUT in web.xml declared:
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
in local environment it start from index.jsp as I expected and declared, but after deploy is not
What you expected to happen:
- app start from index.jsp page. as it declared
I deploy to appEngine with Gradle https://github.com/GoogleCloudPlatform/app-gradle-plugin
I followed that an after adding this to the web.xml file:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
My app started with the index.jsp file even when there was the index.html file too.
I also share with you the section of App Engine of my build.gradle file:
appengine { // App Engine tasks configuration
deploy { // deploy configuration
projectId = System.getenv('GOOGLE_CLOUD_PROJECT')
version = 'GCLOUD_CONFIG'
}
}
I'm migration my GAE Java app to Google Cloud Endpoints 2.0. I followed the migration guide (https://cloud.google.com/endpoints/docs/frameworks/legacy/v1/java/migrating) to migrate to Endpoints v2.0.
The endpoints service calls are working on localhost but returning 404 (Not Found) when uploaded to the App Engine. I'm using Guice.
The relevant section from my web.xml looks similar to this:
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.xyz.myapp.server.guice.MyAppGuiceServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>EndpointsServlet</servlet-name>
<servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.xyz.myapp.server.endpoints.MyEP1,com.xyz.myapp.server.endpoints.MyEP2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>EndpointsServlet</servlet-name>
<url-pattern>/_ah/api/*</url-pattern>
</servlet-mapping>
MyAppGuiceServletContextListener.java
public class MyAppGuiceServletContextListener extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new GuiceServletModule(), new EPServletModule());
}
}
EPServletModule.java
public class EPServletModule extends EndpointsModule {
#Override
protected void configureServlets() {
super.configureServlets();
Set<Class<?>> serviceClasses = new HashSet<Class<?>>();
serviceClasses.add(MyEP1.class);
serviceClasses.add(MyEP2.class);
configureEndpoints("/_ah/api/*", serviceClasses);
}
}
GuiceServletModule:
public class GuiceServletModule extends ServletModule {
#Override
protected void configureServlets() {
super.configureServlets();
serve("/myapp/servlet1").with(Servlet1.class);
serve("/myapp/servlet2").with(Servlet2.class);
}
}
I'm able to invoke the regular servlets at the paths:
https://[version]-dot-myapp-id.appspot.com/myapp/servlet1
https://[version]-dot-myapp-id.appspot.com/myapp/servlet2
But I'm not able to access the endpoints. It always returns 404 error code. I tried via my Javascript client and also via the APIs Explorer, and get the same error.
I also checked the logs and strangely the logs show the POST request like this:
"POST /_ah/spi/com.xyz.myapp.server.endpoints.MyEP1.myEPMethod HTTP/1.1" 404
Why does it start with /_ah/spi/ when my client is invoking it via /_ah/api/ ?
NOTE: I'm able to invoke the Endpoints 1.0, which are deployed on a different version of the same app, without any issue. But the Endpoints 2.0 version is not working.
Am I missing something?
I also have a very basic question. My client is Javascript based. Does it really make use of the Discovery Document?
I fixed this by Updating Android Studio to the latest version and updating the SDK.
Instead of servlet mapping in the web.xml, i'm trying to use annotation to map the servlet to urls as follows:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#WebServlet(name = "GuestbookServlet", urlPatterns = "/guestbook")
public class GuestbookServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.getWriter().println("Hello, world");
}
}
And I have also declared the 3.0 spec for servlet in web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="false">
</web-app>
However, when I run it on my local environment the response returned is 404.
It works however if I just map the servlets in the web.xml. What am I doing wrong? Does GAE still not support 3.0 specs?
Servlet 3.0 spec is not supported by GAE/J
It's still on the roadmap: https://developers.google.com/appengine/docs/features#roadmap_features
You can star this issue to help show your support for this feature: https://code.google.com/p/googleappengine/issues/detail?id=3091
This ticket has been opened a lonnnggg time though.
When I run the following JSP code,
Server info == <%=application.getServerInfo()%><br/>
Major==<%=application.getMajorVersion()%><br/>
Minor==<%=application.getMinorVersion()%><br/>
JSP version is <%= JspFactory.getDefaultFactory().getEngineInfo().getSpecificationVersion()%><br/>
I see
Server info == Google App Engine/Google App Engine/1.8.1
Major==2
Minor==5
JSP version is 2.1
You could run it for yourself.
Servlet 3.1 are now supported on AppEngine.
... In addition to support for an updated JDK and Jetty 9 with Servlet 3.1 specs...
Announcement:
https://cloudplatform.googleblog.com/2017/06/Google-App-Engine-standard-now-supports-Java-8.html
Here is a code example
So i want to use Guice in Appengine with Cloud Endpoints to inject my services, or daos - pretty common I guess, but I found no tutorial for this.
Official Guice for Appengine documentation seems to be here: https://github.com/google/guice/wiki/GoogleAppEngine
When configuring Guice you set up the com.google.inject.servlet.GuiceFilter to intercept every request "/*". And at some point you must initialize the modules. Like the documentation says a good place to do that is a ServletContextListener.
One special kind of Module are ServletModules, that map request-Paths to Servlet-Classes, instead of doing this in the web.xml you can now do this programmatically.
Pretty straight forward up until here. But how do I configure Guice to also include the Endpoint-Classes?
Turns out there is a GuiceSystemServiceServletModule that handles exactly this.
public class GuiceSSSModule extends GuiceSystemServiceServletModule {
#Override
protected void configureServlets() {
super.configureServlets();
Set<Class<?>> serviceClasses = new HashSet<Class<?>>();
serviceClasses.add(MyEndpoint.class);
serviceClasses.add(AnotherAndpoint.class);
this.serveGuiceSystemServiceServlet("/_ah/spi/*", serviceClasses);
}
}
Include this module in the Injector construction in your ServletContextListener:
public class MyGSCL extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new GuiceSSSModule(), new BaseModule());
}
}
and use this listener in your web.xml:
<listener>
<listener-class>de.mypkg.MyGSCL</listener-class>
</listener>
Also make sure to include the Guice filter in your web.xml:
<!-- GUICE -->
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Your endpoints will be available under /_ah/api/... again and you can use #Inject in your endpoint classes.
I am trying to get RPC testing using GWT. I am using the default StockWatcher project that is mentioned here, I download the project, I import it, everything works fine.
I then run junitcreator in the StockWatcher project:
/Users/stephen/Work/gwt/gwt-mac-1.6.4/junitCreator -junit /Users/stephen/Applications/eclipse/plugins/org.junit_3.8.2.v20080602-1318/junit.jar -module stockwatcher -eclipse StockWatcher com.google.gwt.sample.stockwatcher.StockWatcherTest
this creates the StockWatcherTest.java in the appropriate test directory, and gives me some hosted and web mode launch files.
I then also added junit.jar to the classpath for this project.
I then modify StockWatcherTest.java to test whether I am capable of making a asynchronous request to the server. Everything looks fine, but when I try to run StockWatcherTest.java in hosted mode, I get the following error:
Starting HTTP on port 0 HTTP
listening on port 49569
The development shell servlet received a
request for 'greet' in module
'com.google.gwt.sample.stockwatcher.StockWatcher.JUnit.gwt.xml'
[WARN] Resource not found: greet;
(could a file be missing from the
public path or a tag
misconfigured in module
com.google.gwt.sample.stockwatcher.StockWatcher.JUnit.gwt.xml
?)
com.google.gwt.user.client.rpc.StatusCodeException:
Cannot find resource 'greet' in the
public path of module
'com.google.gwt.sample.stockwatcher.StockWatcher.JUnit'
Here is my StockWatcherTest.java class
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* GWT JUnit tests must extend GWTTestCase.
*/
public class StockWatcherTest extends GWTTestCase {
/**
* Must refer to a valid module that sources this class.
*/
public String getModuleName() {
return "com.google.gwt.sample.stockwatcher.StockWatcher";
}
/**
* Add as many tests as you like.
*/
public void testSimple() {
GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
greetingService.greetServer("Bob",
new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
// Show the RPC error message to the user
System.out.println(caught);
fail("big time failure");
finishTest();
}
public void onSuccess(String result) {
System.out.println("success, biatch");
assertTrue(true);
}
});
delayTestFinish(1000);
}
}
Here is com/google/gwt/sample/stockwatcher/StockWatcher.gwt.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.2//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.2/distro-source/core/src/gwt-module.dtd">
<module rename-to='stockwatcher'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.standard.Standard'/>
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->
<!-- Other module inherits -->
<!-- Specify the app entry point class. -->
<entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/>
</module>
and here is web.xml in my generated war
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>StockWatcher.html</welcome-file>
</welcome-file-list>
<!-- Servlets -->
<servlet>
<servlet-name>greetServlet</servlet-name>
<servlet-class>com.google.gwt.sample.stockwatcher.server.GreetingServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>greetServlet</servlet-name>
<url-pattern>/stockwatcher/greet</url-pattern>
</servlet-mapping>
</web-app>
So what am I doing wrong? Any help is appreciated. Thank you.
1-you need to add "finishTest();" at the end of the "onSuccess" method.
2-And to resolve the exeption you got : add in your StockWatcher.gwt.xml the path to your servlet greet.
servlet path='/greet' class='com.google.gwt.sample.stockwatcher.server.GreetingServiceImpl'/
Another solution is using GWT SyncProxy (support both sync & async) to test GWT RPC services in JRE
See the post at http://www.gdevelop.com/w/blog/2010/01/10/testing-gwt-rpc-services/ for details
i made some simple tests for the stock watcher. you can see them at: http://tayek.com/StockWatcher.zip