From b86b9cd568c172d6538fe31a1e2aebb2a93ff23c Mon Sep 17 00:00:00 2001 From: RemRem Date: Tue, 22 Nov 2022 11:04:54 +0100 Subject: [PATCH] init --- .dockerignore | 9 ++ .gitignore | 6 + CHANGELOG.md | 3 + Dockerfile | 21 +++ README.md | 49 ++++++ analysis_options.yaml | 30 ++++ bin/server.dart | 53 +++++++ pubspec.lock | 348 ++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 18 +++ res/red_unicat.png | Bin 0 -> 42262 bytes test/server_test.dart | 39 +++++ 11 files changed, 576 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100755 bin/server.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 res/red_unicat.png create mode 100644 test/server_test.dart diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..21504f8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +Dockerfile +build/ +.dart_tool/ +.git/ +.github/ +.gitignore +.idea/ +.packages diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c8a157 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c333dee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Use latest stable channel SDK. +FROM dart:stable AS build + +# Resolve app dependencies. +WORKDIR /app +COPY pubspec.* ./ +RUN dart pub get + +# Copy app source code (except anything in .dockerignore) and AOT compile app. +COPY . . +RUN dart compile exe bin/server.dart -o bin/server + +# Build minimal serving image from AOT-compiled `/server` +# and the pre-built AOT-runtime in the `/runtime/` directory of the base image. +FROM scratch +COPY --from=build /runtime/ / +COPY --from=build /app/bin/server /app/bin/ + +# Start server. +EXPOSE 8080 +CMD ["/app/bin/server"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e695d9d --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +A server app built using [Shelf](https://pub.dev/packages/shelf), +configured to enable running with [Docker](https://www.docker.com/). + +This sample code handles HTTP GET requests to `/` and `/echo/` + +# Running the sample + +## Running with the Dart SDK + +You can run the example with the [Dart SDK](https://dart.dev/get-dart) +like this: + +``` +$ dart run bin/server.dart +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +## Running with Docker + +If you have [Docker Desktop](https://www.docker.com/get-started) installed, you +can build and run with the `docker` command: + +``` +$ docker build . -t myserver +$ docker run -it -p 8080:8080 myserver +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +You should see the logging printed in the first terminal: +``` +2021-05-06T15:47:04.620417 0:00:00.000158 GET [200] / +2021-05-06T15:47:08.392928 0:00:00.001216 GET [200] /echo/I_love_Dart +``` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/bin/server.dart b/bin/server.dart new file mode 100755 index 0000000..8c053b7 --- /dev/null +++ b/bin/server.dart @@ -0,0 +1,53 @@ +import 'dart:io'; +import 'dart:async'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_router/shelf_router.dart'; +import 'package:path/path.dart' as path; + +// Configure routes. +final _router = Router() + ..get('/', _rootHandler) + ..get('/echo/', _echoHandler) + ..get('/down/', _fileHandler); + +Response _rootHandler(Request req) { + return Response.ok('Hello, World!\n'); +} + +Response _echoHandler(Request req) { + print(req.url); + final message = req.params['message']; + return Response.ok('$message\n'); +} + +Response _fileHandler(Request req) { + final String _basePath = '/home/hel/Projets/r_api/res/'; + final String reqFile = path.join(_basePath, req.params['file']); + File file = File(reqFile ?? ''); //technique du pauvre 2 + Stream> fileStream = file.openRead(); + return Response.ok(fileStream, headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': 'attachment, filename="$reqFile"' + }); +} + +Future fileExist(String path) { + Future exist = File(path).exists(); + print(exist); + return exist; +} + +void main(List args) async { + // Use any available host or container IP (usually `0.0.0.0`). + final ip = 'localhost'; + + // Configure a pipeline that logs requests. + final handler = Pipeline().addMiddleware(logRequests()).addHandler(_router); + + // For running in containers, we respect the PORT environment variable. + final port = int.parse(Platform.environment['PORT'] ?? '8080'); + final server = await serve(handler, ip, port); + print('Server listening on port ${server.port}'); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..6f00e59 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,348 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "49.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + args: + dependency: "direct main" + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http: + dependency: "direct dev" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.5" + http_methods: + dependency: transitive + description: + name: http_methods + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + lints: + dependency: "direct dev" + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shelf: + dependency: "direct main" + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + shelf_router: + dependency: "direct main" + description: + name: shelf_router + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.21.6" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.14" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.18" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "9.4.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.18.2 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..098d23e --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,18 @@ +name: r_api +description: A server app using the shelf package and Docker. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.18.2 <3.0.0' + +dependencies: + args: ^2.0.0 + path: ^1.8.2 + shelf: ^1.1.0 + shelf_router: ^1.0.0 + +dev_dependencies: + http: ^0.13.0 + lints: ^2.0.0 + test: ^1.15.0 diff --git a/res/red_unicat.png b/res/red_unicat.png new file mode 100644 index 0000000000000000000000000000000000000000..b348a92915b0979ae2566d16f8ded261eb7ea535 GIT binary patch literal 42262 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 p.kill()); + + test('Root', () async { + final response = await get(Uri.parse('$host/')); + expect(response.statusCode, 200); + expect(response.body, 'Hello, World!\n'); + }); + + test('Echo', () async { + final response = await get(Uri.parse('$host/echo/hello')); + expect(response.statusCode, 200); + expect(response.body, 'hello\n'); + }); + + test('404', () async { + final response = await get(Uri.parse('$host/foobar')); + expect(response.statusCode, 404); + }); +}