Skip to content

Commit

Permalink
Anvil App Server v1.1
Browse files Browse the repository at this point in the history
Changes:
- Doc updates
- More helpful error messages in several places
- Add a simple Dockerfile
- Fix HTTPS origin handling
- Use the same Python interpreter for the downlink as launcher

Fixes #1, closes #3, Better debug output to investigate #4, Fixes #7

Based-on: anvil 28d8a74b616655716ece2b2f34025cbc12332a46
  • Loading branch information
meredydd committed May 15, 2020
1 parent 4ccb5c4 commit 37a8ff7
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 135 deletions.
8 changes: 8 additions & 0 deletions client/js/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,14 @@ window.loadApp = function(params, preloadModules) {
var appOrigin = params["appOrigin"];
if (appLoaded) { console.log("Rejected duplicate app load"); return {}; }

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(`${appOrigin}/_/service-worker`, {scope: `${appOrigin}`})
.catch((error) => {
console.error('Service worker registration failed:', error);
});
}


var appLoadPromise = loadApp(params["app"], params["appId"], appOrigin, preloadModules);
appLoaded = true;

Expand Down
69 changes: 69 additions & 0 deletions client/js/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
let log = true;

let ACTIVE_CACHE = 'v0';
let OFFLINE_TIMEOUT = 5000;

let lastOffline = 0;

let cleanupOldCaches = async () => {
for (let key of await caches.keys()) {
if (key !== ACTIVE_CACHE) {
console.log("Removing old service worker cache:", key);
await caches.delete(key);
}
}
}


let _fetch = async e => {

let cache = await caches.open(ACTIVE_CACHE);
let match = await cache.match(e.request);

if (!navigator.onLine || lastOffline > Date.now() - OFFLINE_TIMEOUT) {
// Shortcut: Use cache if a request recently failed for something in the cache.
if (match) {
log && console.log("Fast offline cache hit:", e.request.url);
return match;
} else {
log && console.log("Fast offline cache miss:", e.request.url);
}
}

try {
let resp = await fetch(e.request.clone());
lastOffline = 0;

// Allow caching anything that comes back with the X-Anvil-Cacheable header
if (e.request.method === "GET" && resp.status === 200 && resp.headers.has("X-Anvil-Cacheable")) {
log && console.log("Caching:", e.request.url);
cache.put(e.request, resp.clone());
} else {
log && console.log("Not caching:", e.request.url);
cache.delete(e.request);
}
return resp;
} catch (err) {
if (match) {
lastOffline = Date.now();
console.log("Serving Anvil resources from Service Worker cache");
log && console.log("Offline cache hit:", e.request.url);
return match;
} else {
log && console.log("Offline cache miss:", e.request.url);
throw err;
}
}
};

addEventListener('install', e => {
console.log("Service Worker installed with scope:", registration.scope);
});

addEventListener('activate', e => {
e.waitUntil(cleanupOldCaches());
});

addEventListener('fetch', e => {
e.respondWith(_fetch(e))
});
3 changes: 2 additions & 1 deletion client/js/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ var webpack = require("webpack");
module.exports = {
context: path.resolve(__dirname),

// We want to generate two bundles. One for designer, one for runner.
// We want to generate two bundles. One for runner. one for its Service Worker
entry: {
runner: ['babel-polyfill', './runner.js'],
sw: ['babel-polyfill', './sw.js'],
},

// Make PyDefUtils available as window.PyDefUtils
Expand Down
44 changes: 22 additions & 22 deletions client/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

<title>{{app-title}}</title>

<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/bootstrap.css?buildTime=0">
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/bootstrap-theme.min.css?buildTime=0">
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/runner.css?buildTime=0">
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/daterangepicker.css" />
<link rel="stylesheet" href="{{cdn-origin}}/runtime/node_modules/animate.css/animate.min.css" />
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/bootstrap.css?buildTime=0" crossorigin/>
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/bootstrap-theme.min.css?buildTime=0" crossorigin/>
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/runner.css?buildTime=0" crossorigin/>
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/daterangepicker.css" crossorigin/>
<link rel="stylesheet" href="{{cdn-origin}}/runtime/node_modules/animate.css/animate.min.css" crossorigin/>

<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/font-awesome.min.css">
<link rel="stylesheet" href="{{cdn-origin}}/runtime/css/font-awesome.min.css" crossorigin/>

<style>
#loadingSpinner {
Expand All @@ -53,19 +53,19 @@
<style>{{shim-css}}</style>
<style>{{theme-css}}</style>

<script src="{{cdn-origin}}/runtime/node_modules/jquery/dist/jquery.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/jquery-migrate/dist/jquery-migrate.min.js"></script>
<script src="{{cdn-origin}}/runtime/node_modules/jquery/dist/jquery.min.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/node_modules/jquery-migrate/dist/jquery-migrate.min.js" crossorigin></script>

{{head-html}}
</head>
<body class="{{display-header}}">
<div id="anvil-header">
<a href="https://anvil.works/?utm_source=app_banner" target="_blank" class="cta"><span class="hidden-xs">Build web apps for free with</span><span class="visible-xs-inline">Built with</span> Anvil</a>
<a href="https://anvil.works/?utm_source=app_banner" target="_blank"><span class="hidden-xs">Built with </span><img src="{{cdn-origin}}/runtime/img/logo-35-white.png"></a>
<a href="https://anvil.works/?utm_source=app_banner" target="_blank"><span class="hidden-xs">Built with </span><img src="{{cdn-origin}}/runtime/img/logo-35-white.png" crossorigin></a>
</div>

<a id="anvil-badge" href="https://anvil.works/?utm_source=app_banner" target="_blank">
<img src="{{cdn-origin}}/runtime/img/made-with-anvil.png">
<img src="{{cdn-origin}}/runtime/img/made-with-anvil.png" crossorigin>
</a>

<div class="anvil-root-container">
Expand Down Expand Up @@ -121,7 +121,7 @@ <h4 class="modal-title">Session Expired</h4>
<h4 class="modal-title">Log in with Google</h4>
</div>
<div class="modal-body text-center">
<button id="googleLogInButton" type="button" class="btn" data-dismiss="modal"><img src="{{cdn-origin}}/runtime/img/google-signin-buttons/btn_google_signin_light_normal_web.png"/></button>
<button id="googleLogInButton" type="button" class="btn" data-dismiss="modal"><img src="{{cdn-origin}}/runtime/img/google-signin-buttons/btn_google_signin_light_normal_web.png" crossorigin/></button>
</div>
<div class="modal-footer">
<button id="googleCancelButton" type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
Expand Down Expand Up @@ -248,16 +248,16 @@ <h4 class="modal-title">Kerberos Authentication</h4>
<div style="clear:both"></div>
</div>

<script src="{{cdn-origin}}/runtime/node_modules/html5-boilerplate/dist/js/vendor/modernizr-3.8.0.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/html5-boilerplate/dist/js/vendor/modernizr-3.8.0.min.js?buildTime=0" crossorigin></script>

<script src="{{cdn-origin}}/runtime/node_modules/bootstrap/dist/js/bootstrap.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/moment/min/moment.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/moment-timezone/builds/moment-timezone-with-data-2012-2022.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/js/lib/daterangepicker.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/js/lib/bootstrap-notify.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/bootstrap/dist/js/bootstrap.min.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/node_modules/moment/min/moment.min.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/node_modules/moment-timezone/builds/moment-timezone-with-data-2012-2022.min.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/js/lib/daterangepicker.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/js/lib/bootstrap-notify.min.js?buildTime=0" crossorigin></script>

<script src="{{cdn-origin}}/runtime/node_modules/js-yaml/dist/js-yaml.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/js/lib/mutationobserver.min.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/node_modules/js-yaml/dist/js-yaml.min.js?buildTime=0" crossorigin></script>
<script src="{{cdn-origin}}/runtime/js/lib/mutationobserver.min.js?buildTime=0" crossorigin></script>

<script src="https://www.youtube.com/iframe_api"></script>
<script>
Expand All @@ -268,10 +268,10 @@ <h4 class="modal-title">Kerberos Authentication</h4>
<script src="https://checkout.stripe.com/checkout.js"></script>


<script src='{{cdn-origin}}/runtime/js/lib/skulpt.min.js?buildTime=0'></script>
<script src='{{cdn-origin}}/runtime/js/lib/skulpt-stdlib.js?buildTime=0'></script>
<script src='{{cdn-origin}}/runtime/js/lib/skulpt.min.js?buildTime=0' crossorigin></script>
<script src='{{cdn-origin}}/runtime/js/lib/skulpt-stdlib.js?buildTime=0' crossorigin></script>

<script src="{{cdn-origin}}/runtime/js/runner.bundle.js?buildTime=0"></script>
<script src="{{cdn-origin}}/runtime/js/runner.bundle.js?buildTime=0" crossorigin></script>

<script>
window.anvilCDNOrigin = "{{cdn-origin}}";
Expand Down
7 changes: 6 additions & 1 deletion doc/creating-and-editing-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ MyApp
│ └───assets
│ ├── standard-page.html
│ └───theme.css
└── anvil.yaml
├── anvil.yaml
└── LICENSE
```

To learn more about this directory structure and what each of the files is responsible for, see our [app structure guide](app-structure.md).
Expand All @@ -66,6 +67,10 @@ To learn more about this directory structure and what each of the files is respo

Once you've launched the web server with the `anvil-app-server` command, you can simply refresh your browser to see changes you make locally - there's no need to restart the web server!

## Licensing

The sample code provided in these template Anvil apps is permissively licensed for anyone to use. See the 'LICENSE' files in each app directory for more information.




Expand Down
10 changes: 9 additions & 1 deletion doc/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@ $ sudo apt-get install openjdk-8-jdk

On a Mac, you can use Homebrew:
```bash
$ brew cask install java
$ brew install openjdk
```

On Windows systems, you can install [Amazon Corretto](https://aws.amazon.com/corretto/). Any version is OK (Java 8 minimum).

To enable [PDF rendering support](https://anvil.works/docs/media/creating_pdfs) (Linux only), you will need both Chrome and Ghostscript installed. On Debian-based systems, you can do:

```bash
$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
$ sudo dpkg -i google-chrome-stable_current_amd64.deb
$ sudo apt-get install ghostscript
```


### Installing the runtime

Expand Down
4 changes: 2 additions & 2 deletions downlink/python/anvil_downlink_host/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ def fill_out_profiling(self, response_msg, description="Downlink dispatch"):
def init_pdf_worker():
global launch_pdf_worker

if sys.version_info < (3,0,0):
print("Warning: PDF Rendering not supported in Python 2. Renderer not initialised")
if sys.version_info < (3,7,0):
print("Warning: PDF Rendering requires Python 3.7. Renderer not initialised")
elif IS_WINDOWS:
print("Warning: PDF Rendering not supported on Windows. Renderer not initialised")
else:
Expand Down
29 changes: 16 additions & 13 deletions packaging/app-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
FROM ubuntu:18.04
FROM python:3

# This replicates a bug in OpenJDK 11. If you install openjdk-8-jdk, it will work
RUN apt-get -yyy update && apt-get -yyy install openjdk-11-jdk python python-pip
RUN apt-get -yyy update && apt-get -yyy install software-properties-common && \
wget -O- https://apt.corretto.aws/corretto.key | apt-key add - && \
add-apt-repository 'deb https://apt.corretto.aws stable main'

COPY python-package-build /anvil-app-server-bin
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
(dpkg -i google-chrome-stable_current_amd64.deb || apt install -y --fix-broken) && \
rm google-chrome-stable_current_amd64.deb

RUN pip install /anvil-app-server-bin

VOLUME /apps
RUN apt-get -yyy update && apt-get -yyy install java-1.8.0-amazon-corretto-jdk ghostscript

RUN pip install anvil-app-server
RUN anvil-app-server || true

VOLUME /apps
WORKDIR /apps

ENV APP=MainApplication
RUN mkdir /anvil-data

RUN useradd anvil

RUN chown -R anvil:anvil /anvil-data
USER anvil

CMD ["bash", "-c", "anvil-app-server --app $APP"]
ENTRYPOINT ["anvil-app-server", "--data-dir", "/anvil-data"]

# To run:
# mkdir ~/tmp/anvil-apps/.anvil-data
# chown -R 1000 ~/tmp/anvil-apps/.anvil-data
# docker run --rm -v ~/tmp/anvil-apps:/apps -e APP=Feedbackform container-name
CMD ["--app", "MainApp"]
16 changes: 15 additions & 1 deletion packaging/app-server/anvil_app_server/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os.path, sys, subprocess, shlex, zipfile, progressbar
import os.path, re, sys, subprocess, shlex, zipfile, progressbar

if sys.version_info < (3,0,0):
import urllib
Expand All @@ -11,8 +11,16 @@
def launch():
jar_path = find_or_download_app_server()
java_args = ["java", "-jar", jar_path]
if sys.executable:
os.environ["PYTHON_INTERPRETER"] = sys.executable
try:
return_code = subprocess.call(java_args + sys.argv[1:])
except OSError:
print("Failed to launch: java -jar {}".format(jar_path))
print("The Anvil App Server requires a Java Virtual Machine (JVM) installed.")
print("Install with 'apt install openjdk-8-jdk' or 'brew install openjdk', or")
print("download it from https://adoptopenjdk.net")
return_code = 1
except KeyboardInterrupt:
return_code = 0
sys.exit(return_code)
Expand Down Expand Up @@ -55,6 +63,12 @@ def describe_templates():
# Find zip file corresponding to name
filename = TEMPLATES[template_input]['filename']
directory_name = sys.argv[2]

if not re.match('^[A-Za-z_][A-Za-z0-9_]*$', directory_name):
print("\nThe directory '{}' is not a valid Python package name.".format(directory_name))
print("Packages must start with a letter or underscore, and be made up of letters, numbers and underscores.")
sys.exit(1)

package_dir = os.path.dirname(__file__)
package_dir_path = os.path.join(package_dir, filename)

Expand Down
6 changes: 4 additions & 2 deletions packaging/app-server/setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from setuptools import setup,find_packages
setup(
name="anvil-app-server",
version="1.0",
version="1.1",
packages=find_packages(),
install_requires=["pychrome", "anvil-uplink==0.3.30", "progressbar2"],
install_requires=["pychrome", "anvil-uplink==0.3.30", "progressbar2", "wheel"],

# Include the App Server JAR directly in this debug build of the package. That way the release version won't be downloaded on first run.
package_data={
Expand All @@ -26,6 +26,8 @@
- The "downlink" Python code for the server-side parts of apps
- The core Anvil server (requires Java to launch)
- The client-side Anvil runtime (Javascript, using the Skulpt Python-to-JS compiler)
Head over to [the Github Repository](https://github.com/anvil-works/anvil-runtime) for usage instructions, and to learn more.
""",
long_description_content_type="text/markdown",
keywords="anvil web apps standalone browser Python",
Expand Down
5 changes: 5 additions & 0 deletions packaging/app-server/templates/Blank/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This code is based on the 'blank' template from the Anvil App Server. The template code is licensed under the following terms:

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
5 changes: 5 additions & 0 deletions packaging/app-server/templates/Default/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This code is based on the 'hello-world' template from the Anvil App Server. The template code is licensed under the following terms:

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
5 changes: 5 additions & 0 deletions packaging/app-server/templates/TodoList/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This code is based on the 'todo-list' template from the Anvil App Server. The template code is licensed under the following terms:

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2 changes: 2 additions & 0 deletions server/app-server/src/anvil/app_server/conf.clj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

(def-config-var get-client-uplink-key :client-uplink-key)

(def-config-var is-proxied? :proxied?)

(defn set-config! [conf]
(reset! config (-> (merge DEFAULTS conf)
(update-in [:hostname] #(or % (second (re-matches #".*://([^/:]+).*" (str (:url config)))) "localhost"))
Expand Down
3 changes: 2 additions & 1 deletion server/app-server/src/anvil/app_server/dispatch.clj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
(fn []
(while (not @shutting-down?)
(log/info "Launching built-in downlink...")
(let [pb (ProcessBuilder. #^"[Ljava.lang.String;" (into-array String ["python" "-m" "anvil_downlink_host.run"]))
(let [pb (ProcessBuilder. #^"[Ljava.lang.String;" (into-array String [(or (System/getenv "PYTHON_INTERPRETER") "python") "-m" "anvil_downlink_host.run"]))
env (.environment pb)]
(.put env "DOWNLINK_SERVER" (str "ws://" server-host ":" server-port "/_/downlink"))
(.put env "DOWNLINK_KEY" downlink-key)
Expand Down Expand Up @@ -56,6 +56,7 @@

(dispatcher/register-dispatch-handler! ::downlink dispatcher/DOWNLINK-PRIORITY (fn [_req] @downlink))

(defn downlink-connected? [] (boolean @downlink))

(defonce next-uplink-id (atom 0))
;; reg-id -> {:func func-pattern, :uplink-id id, :executor executor}
Expand Down
Loading

0 comments on commit 37a8ff7

Please sign in to comment.