├── .gitattributes
├── .gitignore
├── .mvn
├── jvm.config
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── CONTRIBUTING.md
├── DIRECTORIES_AND_FILES.md
├── LICENSE
├── Makefile
├── README.md
├── RELEASE_NOTES.md
├── docs
├── about_thorough_testing.md
├── acknowledgements.md
├── dead_code_file.txt
├── definition_of_done.txt
├── development_handbook.md
├── getting_started
│ ├── README.md
│ ├── getting_started.md
│ ├── getting_started_part_2.md
│ ├── getting_started_part_3.md
│ ├── getting_started_part_4.md
│ ├── test_error.png
│ ├── ui_finished.png
│ └── ui_in_part_4.png
├── how_to_tell_if_well_tested_code.txt
├── howto
│ ├── add_a_new_endpoint.md
│ └── jlink.md
├── http_protocol
│ ├── http_status_codes_mdn.txt
│ ├── mime_types.txt
│ └── password_storage_cheat_sheet_owasp.txt
├── maven
│ ├── README.md
│ ├── gnupg.tar.gz.encrypted
│ └── pom.xml
├── migration_to_v3.md
├── parable_two_programmers.md
├── perf_data
│ ├── README.md
│ ├── database_speed_test.md
│ ├── datestamp_perf.md
│ ├── framework_perf_comparison.md
│ ├── loom_perf.md
│ └── response_speed_test.md
├── quick_start.md
├── release_messages
│ ├── version1.md
│ └── version3.md
├── simple_minum_program.jpg
├── simplify_then_add_lightness.md
├── size_comparisons.md
├── todo
│ └── done
│ │ ├── BUG_inconsistencies_in_DDPS_code.md
│ │ ├── BUG_tests_added_to_wrong_suite.txt
│ │ ├── a_user_should_be_able_to_add_photo_and_description.txt
│ │ ├── docs_paradigm_of_the_database.txt
│ │ ├── docs_recommended_code_pattern.txt
│ │ ├── docs_test_examining_logs.txt
│ │ ├── docs_testing_an_endpoint.txt
│ │ ├── feature_cache_of_static_responses.txt
│ │ ├── feature_index_page.txt
│ │ ├── remove_static_variables.md
│ │ └── virtual_tests_lock_up_after_reboot
│ │ ├── tests_lock_up_after_reboot.txt
│ │ └── thread_dump.json
└── user_story_checklist.txt
├── minum.config
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
├── README.md
├── main
│ ├── java
│ │ ├── com
│ │ │ └── renomad
│ │ │ │ └── minum
│ │ │ │ ├── README.md
│ │ │ │ ├── database
│ │ │ │ ├── Db.java
│ │ │ │ ├── DbData.java
│ │ │ │ ├── DbException.java
│ │ │ │ ├── README.md
│ │ │ │ └── package-info.java
│ │ │ │ ├── htmlparsing
│ │ │ │ ├── HtmlParseNode.java
│ │ │ │ ├── HtmlParser.java
│ │ │ │ ├── ParseNodeType.java
│ │ │ │ ├── ParsingException.java
│ │ │ │ ├── README.md
│ │ │ │ ├── TagInfo.java
│ │ │ │ ├── TagName.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── logging
│ │ │ │ ├── ILogger.java
│ │ │ │ ├── Logger.java
│ │ │ │ ├── LoggingActionQueue.java
│ │ │ │ ├── LoggingLevel.java
│ │ │ │ ├── README.md
│ │ │ │ ├── TestLogger.java
│ │ │ │ ├── TestLoggerException.java
│ │ │ │ ├── TestLoggerQueue.java
│ │ │ │ ├── ThrowingSupplier.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ ├── queue
│ │ │ │ ├── AbstractActionQueue.java
│ │ │ │ ├── ActionQueue.java
│ │ │ │ ├── ActionQueueKiller.java
│ │ │ │ ├── ActionQueueState.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── security
│ │ │ │ ├── ForbiddenUseException.java
│ │ │ │ ├── ITheBrig.java
│ │ │ │ ├── Inmate.java
│ │ │ │ ├── MinumSecurityException.java
│ │ │ │ ├── README.md
│ │ │ │ ├── TheBrig.java
│ │ │ │ ├── UnderInvestigation.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── state
│ │ │ │ ├── Constants.java
│ │ │ │ ├── Context.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── templating
│ │ │ │ ├── README.md
│ │ │ │ ├── RenderingResult.java
│ │ │ │ ├── TemplateParseException.java
│ │ │ │ ├── TemplateProcessor.java
│ │ │ │ ├── TemplateRenderException.java
│ │ │ │ ├── TemplateSection.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── testing
│ │ │ │ ├── README.md
│ │ │ │ ├── RegexUtils.java
│ │ │ │ ├── StopwatchUtils.java
│ │ │ │ ├── TestFailureException.java
│ │ │ │ ├── TestFramework.java
│ │ │ │ └── package-info.java
│ │ │ │ ├── utils
│ │ │ │ ├── ByteUtils.java
│ │ │ │ ├── ConcurrentSet.java
│ │ │ │ ├── CryptoUtils.java
│ │ │ │ ├── FileReader.java
│ │ │ │ ├── FileUtils.java
│ │ │ │ ├── IFileReader.java
│ │ │ │ ├── InvariantException.java
│ │ │ │ ├── Invariants.java
│ │ │ │ ├── LRUCache.java
│ │ │ │ ├── MyThread.java
│ │ │ │ ├── README.md
│ │ │ │ ├── RingBuffer.java
│ │ │ │ ├── RunnableWithDescription.java
│ │ │ │ ├── SearchUtils.java
│ │ │ │ ├── SerializationUtils.java
│ │ │ │ ├── StacktraceUtils.java
│ │ │ │ ├── StringUtils.java
│ │ │ │ ├── ThrowingRunnable.java
│ │ │ │ ├── TimeUtils.java
│ │ │ │ ├── UtilsException.java
│ │ │ │ └── package-info.java
│ │ │ │ └── web
│ │ │ │ ├── Body.java
│ │ │ │ ├── BodyProcessor.java
│ │ │ │ ├── BodyType.java
│ │ │ │ ├── ContentDisposition.java
│ │ │ │ ├── CountBytesRead.java
│ │ │ │ ├── FullSystem.java
│ │ │ │ ├── FunctionalTesting.java
│ │ │ │ ├── Headers.java
│ │ │ │ ├── HttpServerType.java
│ │ │ │ ├── HttpVersion.java
│ │ │ │ ├── IBodyProcessor.java
│ │ │ │ ├── IInputStreamUtils.java
│ │ │ │ ├── IRequest.java
│ │ │ │ ├── IResponse.java
│ │ │ │ ├── IServer.java
│ │ │ │ ├── ISocketWrapper.java
│ │ │ │ ├── InputStreamUtils.java
│ │ │ │ ├── InvalidRangeException.java
│ │ │ │ ├── LastMinuteHandlerInputs.java
│ │ │ │ ├── Partition.java
│ │ │ │ ├── PathDetails.java
│ │ │ │ ├── PreHandlerInputs.java
│ │ │ │ ├── README.md
│ │ │ │ ├── Range.java
│ │ │ │ ├── Request.java
│ │ │ │ ├── RequestLine.java
│ │ │ │ ├── Response.java
│ │ │ │ ├── Server.java
│ │ │ │ ├── SetOfSws.java
│ │ │ │ ├── SocketWrapper.java
│ │ │ │ ├── StatusLine.java
│ │ │ │ ├── StreamingMultipartPartition.java
│ │ │ │ ├── ThrowingConsumer.java
│ │ │ │ ├── ThrowingFunction.java
│ │ │ │ ├── UrlEncodedDataGetter.java
│ │ │ │ ├── UrlEncodedKeyValue.java
│ │ │ │ ├── WebEngine.java
│ │ │ │ ├── WebFramework.java
│ │ │ │ ├── WebServerException.java
│ │ │ │ └── package-info.java
│ │ └── module-info.java
│ └── resources
│ │ └── certs
│ │ ├── README.txt
│ │ └── keystore
└── test
│ ├── java
│ └── com
│ │ └── renomad
│ │ └── minum
│ │ ├── EqualsTests.java
│ │ ├── FunctionalTests.java
│ │ ├── SearchHelpers.java
│ │ ├── TheRegister.java
│ │ ├── database
│ │ └── DbTests.java
│ │ ├── htmlparsing
│ │ ├── HtmlParseNodeTests.java
│ │ ├── HtmlParserTests.java
│ │ └── TagInfoTests.java
│ │ ├── logging
│ │ ├── CustomLoggingLevel.java
│ │ ├── DescendantLogger.java
│ │ ├── LoggerTests.java
│ │ ├── LoggingActionQueueTests.java
│ │ └── TestLoggerTests.java
│ │ ├── sampledomain
│ │ ├── ListPhotos.java
│ │ ├── LruCacheTests.java
│ │ ├── PersonName.java
│ │ ├── README.md
│ │ ├── SampleDomain.java
│ │ ├── UploadPhoto.java
│ │ ├── auth
│ │ │ ├── AuthResult.java
│ │ │ ├── AuthUtils.java
│ │ │ ├── LoginResult.java
│ │ │ ├── LoginResultStatus.java
│ │ │ ├── LoopingSessionReviewing.java
│ │ │ ├── README.md
│ │ │ ├── RegisterResult.java
│ │ │ ├── RegisterResultStatus.java
│ │ │ ├── SessionId.java
│ │ │ ├── User.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── photo
│ │ │ ├── Photograph.java
│ │ │ ├── README.md
│ │ │ ├── Video.java
│ │ │ └── package-info.java
│ │ ├── security
│ │ └── TheBrigTests.java
│ │ ├── state
│ │ └── ConstantsTests.java
│ │ ├── templating
│ │ ├── Stock.java
│ │ ├── TemplateSectionTests.java
│ │ └── TemplatingTests.java
│ │ ├── testing
│ │ ├── README.md
│ │ ├── RegexUtilsTests.java
│ │ └── TestFrameworkTests.java
│ │ ├── utils
│ │ ├── ActionQueueKillerTests.java
│ │ ├── ActionQueueTests.java
│ │ ├── ByteUtilsTests.java
│ │ ├── CryptoUtilsTests.java
│ │ ├── FileReaderTests.java
│ │ ├── FileUtilsTests.java
│ │ ├── GzipTests.java
│ │ ├── InvariantsTests.java
│ │ ├── MyThreadTests.java
│ │ ├── RingBufferTests.java
│ │ ├── RunnableWithDescriptionTests.java
│ │ ├── SearchUtilsTests.java
│ │ ├── SerializationUtilsTests.java
│ │ ├── StackTraceUtilsTests.java
│ │ ├── StringUtilsTests.java
│ │ ├── ThrowingRunnableTests.java
│ │ └── TimeUtilsTests.java
│ │ └── web
│ │ ├── BodyProcessorTests.java
│ │ ├── BodyTests.java
│ │ ├── EndpointTests.java
│ │ ├── FakeBodyProcessor.java
│ │ ├── FakeRequest.java
│ │ ├── FakeSocketWrapper.java
│ │ ├── FullSystemTests.java
│ │ ├── FunctionalTestingTests.java
│ │ ├── HeadersTests.java
│ │ ├── InputStreamUtilsTests.java
│ │ ├── PathDetailsTests.java
│ │ ├── RangeTests.java
│ │ ├── RequestLineTests.java
│ │ ├── RequestTests.java
│ │ ├── ResponseTests.java
│ │ ├── ServerTests.java
│ │ ├── SetOfSwsTests.java
│ │ ├── SocketWrapperTests.java
│ │ ├── WebEngineTests.java
│ │ ├── WebFrameworkTests.java
│ │ └── WebTests.java
│ ├── resources
│ ├── gettysburg_address.txt
│ ├── html_fuzzer.html
│ ├── kitty.jpg
│ └── video_poster.jpg
│ └── webapp
│ ├── README.md
│ ├── static
│ ├── Foo
│ ├── index.html
│ ├── index.js
│ ├── listphotos
│ │ └── list_photos.css
│ ├── main.css
│ ├── moon.png
│ ├── moon.webp
│ └── uploadphoto
│ │ ├── upload_photo.css
│ │ └── upload_photo.js
│ └── templates
│ ├── auth
│ ├── login_page_template.html
│ ├── logout_page_template.html
│ └── register_page_template.html
│ ├── listphotos
│ ├── list_photos_template.html
│ └── video_element_template.html
│ ├── sampledomain
│ ├── auth_homepage.html
│ ├── name_entry.html
│ └── unauth_homepage.html
│ ├── templatebenchmarks
│ ├── expected_stock_output.html
│ ├── expected_stock_output_parsed.txt
│ ├── individual_stock.html
│ └── stock_prices.html
│ └── uploadphoto
│ ├── upload_photo_template.html
│ └── upload_video_template.html
└── utils
└── build_manifest.sh
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=lf
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.java text
7 | *.sh text
8 | *.txt text
9 |
10 | # Denote all files that are truly binary and should not be modified.
11 | *.png binary
12 | *.jpg binary
13 | *.jar binary
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # don't add any built Java files
2 | *.class
3 |
4 | # ignore the built stuff
5 | out/
6 | target/
7 |
8 | # don't want to commit any Intellij config files
9 | .idea
10 | *.iml
11 |
12 | # Don't want any of those Mac finder junk files scattered all over
13 | .DS_Store
14 |
15 | # Don't store the minum.database we create with our application
16 | # that is in the root directory (there is a sample in the docs directory
17 | # that we *will* store for use in local testing)
18 | db/
19 |
20 | # This file is just a flag to show that the system is running.
21 | SYSTEM_RUNNING
22 |
23 | # Sometimes the Java compiler will fail and output these files, like javac.20230430_000504.args
24 | *.args
25 |
26 | # One of the functional tests puts a file in the static directory that is very large.
27 | # we don't want to add this to our repo.
28 | src/test/webapp/static/largefile.txt
29 |
30 | # Eclipse config files
31 | *.prefs
32 | .classpath
33 | .project
34 |
--------------------------------------------------------------------------------
/.mvn/jvm.config:
--------------------------------------------------------------------------------
1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
2 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
3 | --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
4 | --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
5 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
6 | --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
7 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
8 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
9 | --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
10 | --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byronka/minum/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
19 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to the Minum web framework
2 | =======================================
3 |
4 | This is an opinionated minimalist web framework to develop
5 | server-side-rendered monolith hypermedia html-first web applications.
6 |
7 | The thinking behind this project is described in the
8 | [development handbook](docs/development_handbook.md/#on-minimalism).
9 |
10 | Emphasis is on developing the leanest interface with the fewest
11 | technologies, written as frugally and plainly as possible.
12 | Performance, security, and maintainability are vital.
13 |
14 | What is needed
15 | --------------
16 |
17 | * Clear documentation
18 | * Examples
19 | * Refinements and refactorings
20 | * Fresh ideas (but staying minimalist)
21 | * Reporting security vulnerabilities
22 | * Performance tuning
23 | * Bug removal
24 | * Expanded testing
25 |
26 |
27 | Please create a discussion to explain the situation before getting into coding.
28 |
29 | If desiring to add a new capability to the system, describe how it
30 | will benefit the user, with sufficient examples. New features will
31 | require a compelling explanation of the anticipated benefits.
32 |
33 | The system will stay at 100% statement and branch coverage. While it is not absolutely necessary
34 | that your code get to that level, it is appreciated to provide good testing for
35 | any new functionality. This is a good time to repeat that any new functionality should
36 | start with a discussion, so that your effort is not wasted.
37 |
--------------------------------------------------------------------------------
/DIRECTORIES_AND_FILES.md:
--------------------------------------------------------------------------------
1 |
2 | Directories:
3 | ------------
4 |
5 | - docs: documentation for the project
6 | - .git: necessary files for Git source-code management.
7 | - .mvn: necessary files for the Maven wrapper
8 | - src: All the source code
9 | - utils: scripts and utilities for the system
10 | - out: artifacts from the publishing process
11 | - target: the default Maven build directory, built after it runs
12 |
13 |
14 | Root-level files:
15 | -----------------
16 |
17 | - DIRECTORIES_AND_FILES.md: this file
18 | - .gitignore: files we want Git to ignore.
19 | - .gitattributes: configuration for Git
20 | - LICENSE: the license that applies to this code
21 | - Makefile: the configuration for Gnu Make, which is part of our build tools
22 | - minum.config: a configuration file for the running app (a local / test-oriented version)
23 | - mvnw and mvnw.cmd: the maven wrapper - provides an ability to run Maven commands without needing to install Maven
24 | - pom.xml: Maven configuration
25 | - README.md: A surface-level explainer of the project
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Byron Katz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/docs/acknowledgements.md:
--------------------------------------------------------------------------------
1 | Acknowledgements
2 | ================
3 |
4 | I appreciate people taking the time to give advice:
5 |
6 | - Matthew Taylor - Thanks for the reviews
7 | - Elliott Frisch - Thanks for the advice
8 | - Chris Carroll - Appreciate you taking the time for a run-through
9 | - Shawn McManus - Thanks for the encouragement
10 | - Matt Grasberger, Adam Hamrick (https://github.com/kalverra), Max Saperstone - Thanks for the encouragement and advice
--------------------------------------------------------------------------------
/docs/dead_code_file.txt:
--------------------------------------------------------------------------------
1 | The following is a record of commits where significant reduction of dead code took place.
2 |
3 | Some of this dead code may come in handy in the future, but was not used at the time.
4 |
5 | SHA Description
6 | fb685570 simple ORM for external db, r3z-style minum.database
7 | 84c24363 Crypto utilities - hashing
8 | 27d1f189 HTTP/2
9 | v7.0.0 http transfer by chunked encoding
--------------------------------------------------------------------------------
/docs/definition_of_done.txt:
--------------------------------------------------------------------------------
1 | 1. coding is complete
2 | 2. code reviewed
3 | 3. user story checklist considered
4 | 4. automation runs successfully
5 | 5. manual careful review
6 | 6. validation by customer/user (if available)
7 |
--------------------------------------------------------------------------------
/docs/getting_started/README.md:
--------------------------------------------------------------------------------
1 | Getting Started tutorial
2 | ========================
3 |
4 | A tutorial for getting familiar with use of the framework from the ground up.
5 |
6 | Start [here](getting_started.md)
--------------------------------------------------------------------------------
/docs/getting_started/test_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byronka/minum/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/docs/getting_started/test_error.png
--------------------------------------------------------------------------------
/docs/getting_started/ui_finished.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byronka/minum/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/docs/getting_started/ui_finished.png
--------------------------------------------------------------------------------
/docs/getting_started/ui_in_part_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byronka/minum/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/docs/getting_started/ui_in_part_4.png
--------------------------------------------------------------------------------
/docs/how_to_tell_if_well_tested_code.txt:
--------------------------------------------------------------------------------
1 | how to tell if code is well tested?
2 |
3 | run a test. Count the number of lines run. Fewer lines being run by a test is better than more lines.
4 | for a given line, how many times is it touched when all the tests run - overall, being called more may correlate to better testing
5 | for a given function, how many times is it called? overall, being called more may correlate to better testing
6 | for a given statement, how many mutations will cause its test to break?
7 | for a given predicate, what percentage of MC/DC coverage are we hitting?
8 | what is the overall code coverage
9 | what is the mutation-affected code coverage?
10 | for a parameter's type, how many of the basic boundary values are hit by the tests? How many of the edges of the boundaries? if nullable, is a null sent? if a container, is an empty container sent? if a string, is an empty string sent? and so on. Consider basic negative tests for various types.
11 | on top of several statistics, how to counter gaming (creating loops to call methods / lines many times to raise the value). Perhaps it is a matter of weighing each dimension properly, to prevent any one dimension from controlling.
12 |
13 |
--------------------------------------------------------------------------------
/docs/howto/jlink.md:
--------------------------------------------------------------------------------
1 | # Creating Application images with Jlink and Minum
2 |
3 | ## 1. Copy all your dependencies to `target/modules`
4 |
5 | The following plugin configuration will copy the application jar and all the runtime dependencies to `target/modules`.
6 | ```xml
7 |
10 | * For example, a p (paragraph) or div (division)
11 | *
18 | * For example, {@code Hi I am the content
\> mvn clean compile
\> (minum) make clean jar | 3.4 | 1.5 |
9 | | Start Time (Sec)
\> java -jar app.jar | 0.3 | 1.6 |
10 | | Requests Per Second
\> ab -k -c 20 -n 1000000 http://localhost/8080/hello/John
Single thread | 19k | 18k |
11 | | Requests Per Second with -Xmx16m
\> ab -k -c 20 -n 1000000 http://localhost:8080/hello/John
Single thread | 19k | 10k |
12 | | Requests Per Second with -Xmx64m
\> wrk -t12 -c400 -d30s --latency http://localhost:8080/hello/John
12 threads and 400 connections | 8.9k | 4.2k |
13 | | Memory Consumption - Heap Usage (Mb) | 50 | 95 |
14 | | Memory Consumption - Heap usage with -Xmx16m (Mb) | 8.6 | 10.5 |
15 | | Jar Size With Dependencies (Mb) | 0.2 | 19 |
--------------------------------------------------------------------------------
/docs/perf_data/loom_perf.md:
--------------------------------------------------------------------------------
1 | Project Loom
2 | ============
3 |
4 | "Project Loom" is the name given to the work being done to enable green threads in the Java VM.
5 | This will allow us to have millions of threads, which is perfect for our use case.
6 |
7 | Here is some code to sample this. While running this code, have Java Mission Control running
8 | and keep an eye on the live threads. In the first chunk of code (which uses ordinary threads),
9 | each thread will take up an OS thread and about 2 megabytes of memory - about 8 gigabytes gets used.
10 |
11 | In the next chunk, it's using virtual threads, and you will see it use maybe 30 threads and maybe
12 | 50 megabytes. Quite a difference!
13 |
14 | ```java
15 | System.out.println("Starting lots of threads"); {
16 |
17 | var threadFactory = Thread.ofPlatform().factory();
18 | try (var executor = Executors.newThreadPerTaskExecutor(threadFactory)) {
19 | IntStream.range(0, 10_000).forEach(i -> {
20 | executor.submit(() -> {
21 | Thread.sleep(Duration.ofSeconds(100));
22 | return i;
23 | });
24 | });
25 | }
26 | }
27 |
28 | System.out.println("Starting virtual threads");{
29 |
30 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
31 | IntStream.range(0, 10_000).forEach(i -> {
32 | executor.submit(() -> {
33 | Thread.sleep(Duration.ofSeconds(100));
34 | return i;
35 | });
36 | });
37 | }
38 | }
39 | ```
40 |
41 | Still, the issue remains: When I try using this virtual thread executor, my tests fail
42 | in seemingly capricious ways. Not good. Still waiting on this to stabilize.
43 |
--------------------------------------------------------------------------------
/docs/perf_data/response_speed_test.md:
--------------------------------------------------------------------------------
1 | Response time test
2 | ==================
3 |
4 | #### In short: 19,500 responses per second
5 |
6 | This performance test was run on a Mac M2
7 |
8 | ```shell
9 | $ ab -k -c20 -n 1000000 "http://localhost:8080/hello?name=byron"
10 | This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
11 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
12 | Licensed to The Apache Software Foundation, http://www.apache.org/
13 |
14 | Benchmarking localhost (be patient)
15 | Completed 100000 requests
16 | Completed 200000 requests
17 | Completed 300000 requests
18 | Completed 400000 requests
19 | Completed 500000 requests
20 | Completed 600000 requests
21 | Completed 700000 requests
22 | Completed 800000 requests
23 | Completed 900000 requests
24 | Completed 1000000 requests
25 | Finished 1000000 requests
26 |
27 |
28 | Server Software: minum
29 | Server Hostname: localhost
30 | Server Port: 8080
31 |
32 | Document Path: /hello?name=byron
33 | Document Length: 11 bytes
34 |
35 | Concurrency Level: 20
36 | Time taken for tests: 51.167 seconds
37 | Complete requests: 1000000
38 | Failed requests: 0
39 | Keep-Alive requests: 1000000
40 | Total transferred: 150000000 bytes
41 | HTML transferred: 11000000 bytes
42 | Requests per second: 19543.92 [#/sec] (mean)
43 | Time per request: 1.023 [ms] (mean)
44 | Time per request: 0.051 [ms] (mean, across all concurrent requests)
45 | Transfer rate: 2862.88 [Kbytes/sec] received
46 |
47 | Connection Times (ms)
48 | min mean[+/-sd] median max
49 | Connect: 0 0 0.0 0 4
50 | Processing: 0 1 0.4 1 33
51 | Waiting: 0 1 0.4 1 29
52 | Total: 0 1 0.4 1 33
53 |
54 | Percentage of the requests served within a certain time (ms)
55 | 50% 1
56 | 66% 1
57 | 75% 1
58 | 80% 1
59 | 90% 1
60 | 95% 2
61 | 98% 2
62 | 99% 2
63 | 100% 33 (longest request)
64 |
65 | ```
--------------------------------------------------------------------------------
/docs/quick_start.md:
--------------------------------------------------------------------------------
1 | Quick Start
2 | ===========
3 |
4 | This software will enable you to create web applications in Java. It provides
5 | the bare minimum of what is necessary for that task, plainly and simply. This quick
6 | start assumes you have a Posix environment, and have Java 21 or higher installed. If
7 | not, see [environment](#environment)
8 |
9 |
10 | Step 1 - Download the example
11 | -----------------------------
12 |
13 | Grab this project which demonstrates a simple approach to using Minum.
14 |
15 | Using Git:
16 |
17 | ```shell
18 | git clone https://github.com/byronka/minum_usage_example_smaller.git
19 | ```
20 |
21 | _If you don't have Git_, you can download a zip file of Minum, which will need to be unzipped:
22 |
23 | https://github.com/byronka/minum_usage_example_smaller/archive/refs/heads/master.zip
24 |
25 |
26 | Step 2 - run the example
27 | ------------------------
28 |
29 | Run this command in its directory:
30 |
31 | ```shell
32 | ./mvnw compile exec:java
33 | ```
34 |
35 | It will compile and you will be able to view it at http://localhost:8080
36 |
37 |
38 | Step 3 - Think about the example
39 | --------------------------------
40 |
41 | Let's look at the code:
42 |
43 |
44 |
45 |
46 | Step 4 - modify the example
47 | ---------------------------
48 |
49 | * Stop the server and restart by running `./mvnw compile exec:java`
50 | * Change the path - have it serve content from /hi instead of /hello
51 |
52 | Next steps
53 | ----------
54 |
55 | Now you are ready to go further. If you want a step-by-step tutorial on building a
56 | project with Minum from the ground up, check out the [getting started tutorial](getting_started/getting_started.md).
57 |
58 | Or, you may want to pore through a [larger example](https://github.com/byronka/minum_usage_example_mvn)
59 |
60 | Have fun!
61 |
62 |
63 |
64 | Environment
65 | -----------
66 |
67 | To work with the Minum framework, it is required to have Java 21 or beyond installed. Also,
68 | the development has been done in Posix environments, like the Bash or Zsh shells, or Cygwin
69 | on Windows.
70 |
71 | Try this in your shell:
72 |
73 | ```shell
74 | javac -version
75 | ```
76 |
77 | The result should be `javac 21` or higher. If it not, check
78 | out [Step-by-step guide to installing Java on Windows](development_handbook.md#step-by-step-guide-for-installing-java-on-windows)
79 | or [Java on Mac](development_handbook.md#java-on-mac)
80 |
81 | ***After changing environment variables, you must close and reopen your terminal to see the change***
82 |
83 | Make sure to have the JAVA_HOME environment variable set. Test like this:
84 |
85 | ```shell
86 | echo $JAVA_HOME
87 | ```
88 |
89 | The output should be the directory where Java is installed, but *not* the bin
90 | directory where java and javac live. Try this (this command changes directory to
91 | JAVA_HOME and then lists the files there):
92 |
93 | ```shell
94 | cd $JAVA_HOME
95 | ls
96 | ```
97 |
98 | You should see results like: `bin conf include jmods legal lib release`
99 |
100 | This is why your `PATH` environment variable should include something like this:
101 |
102 | ```shell
103 | $JAVA_HOME/bin
104 | ```
--------------------------------------------------------------------------------
/docs/release_messages/version1.md:
--------------------------------------------------------------------------------
1 | I am happy to announce my minimalist zero-dependency web framework, Minum, is out of beta.
2 | http://github.com/byronka/minum
3 |
4 | You will be hard-pressed to find another modern project as obsessively minimalistic. Other frameworks will claim
5 | simplicity and minimalism and then, casually, mention they are built on a multitude of libraries. This follows
6 | self-imposed constraints, predicated on a belief that smaller and lighter is long-term better.
7 |
8 | Caveat emptor: This is a project by and for developers who know and like programming (rather than, let us say,
9 | configuring). It is written in Java, and presumes familiarity with the HTTP/HTML paradigm.
10 |
11 | Driving paradigms of this project:
12 |
13 | * ease of use
14 | * maintainability / sustainability
15 | * simplicity
16 | * performance
17 | * good documentation
18 | * good testing
19 |
20 | It requires Java 21, for its virtual threads (Project Loom)
--------------------------------------------------------------------------------
/docs/simple_minum_program.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byronka/minum/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/docs/simple_minum_program.jpg
--------------------------------------------------------------------------------
/docs/todo/done/BUG_inconsistencies_in_DDPS_code.md:
--------------------------------------------------------------------------------
1 | There's too many inconsistent patterns in DDPS
--------------------------------------------------------------------------------
/docs/todo/done/BUG_tests_added_to_wrong_suite.txt:
--------------------------------------------------------------------------------
1 | When reviewing the report of tests, noticed that the last test in a suite gets
2 | included in the next suite. Fix.
--------------------------------------------------------------------------------
/docs/todo/done/a_user_should_be_able_to_add_photo_and_description.txt:
--------------------------------------------------------------------------------
1 | It should be possible for a user of the system to add a new photo with
2 | a description, and for that information to become available publicly
3 | (without requiring authentication).
4 |
5 | This is to start off an idea for a genealogical program. We will
6 | follow the typical iterative approach where we try to determine the
7 | simplest and smallest new functionality that aligns with our
8 | overarching need.
9 |
10 | Tasks:
11 | 1. ability to receive photos - requires reading form/multipart data DONE
12 | 2. simple web page to send a photo: required: short description (3-20 chars), long description (0-1000 chars), the photo itself
13 | 3. simple page to view all photos, with their short and long descriptions
14 |
--------------------------------------------------------------------------------
/docs/todo/done/docs_paradigm_of_the_database.txt:
--------------------------------------------------------------------------------
1 | write essay on paradigm of our database - when to use it, when not, fat data vs thin, etc
2 |
3 | The database design for Minum is intended to prioritize simplicity and minimalism
4 | over all else. It does not provide the kind of safety you would expect with an
5 | ACID-compliant database. It does not have the cornucopia of features you would
6 | expect from a database handling enterprise-style risks.
7 |
8 | What it does have, however, is a nearly-absurd sparseness, good performance, and
9 | ease of use. This means for low-risk uses, of which there are quite a number,
10 | this small program might be just the ticket.
--------------------------------------------------------------------------------
/docs/todo/done/docs_recommended_code_pattern.txt:
--------------------------------------------------------------------------------
1 | How the code should be organized if you intend to follow the overarching patterns here.
2 |
3 | WON'T DO
--------------------------------------------------------------------------------
/docs/todo/done/docs_test_examining_logs.txt:
--------------------------------------------------------------------------------
1 | If you want to write a test that inspects the logs
--------------------------------------------------------------------------------
/docs/todo/done/docs_testing_an_endpoint.txt:
--------------------------------------------------------------------------------
1 | How to test one, avoiding network as much as possible
2 |
3 | WON'T DO
--------------------------------------------------------------------------------
/docs/todo/done/feature_cache_of_static_responses.txt:
--------------------------------------------------------------------------------
1 | done 9/29/2022
2 |
3 | Create a cache of static responses so the web server can quickly respond
4 | when it receives a request for something that only changes between restarts.
5 |
6 | Things like CSS files, JS scripts, images.
7 |
8 | make sure there is a working "resources/static" directory - actually test this thing out.
--------------------------------------------------------------------------------
/docs/todo/done/feature_index_page.txt:
--------------------------------------------------------------------------------
1 | done 10/1/22
2 |
3 | Put together a highly graphical and interesting index page
4 |
5 | Maybe something with gif animation, or JavaScript? Better if
6 | it's lighter, maybe involve someone who knows colors and graphics better?
7 |
8 | Not really "highly graphical" or interesting, but whatevs.
--------------------------------------------------------------------------------
/docs/user_story_checklist.txt:
--------------------------------------------------------------------------------
1 | User story code-review checklist:
2 |
3 | The following is used during story development as a reminder to the development team
4 | what is needed for high-quality software.
5 |
6 | [ ] risks carefully considered
7 |
8 | functional development and considerations
9 | documented thoughtfully
10 | [ ] classes
11 | [ ] methods
12 | [ ] tests
13 | [ ] unusual aspects documented within code
14 | [ ] READMEs
15 | [ ] developer documentation
16 | [ ] user documentation
17 | [ ] log entries added
18 | correctness
19 | [ ] unit tests written
20 | [ ] were the tests thorough, or only superficial?
21 | [ ] invariants applied - e.g. check(val > 10)
22 | [ ] integration tests written
23 |
24 | non-functionals:
25 | [ ] perf (what parts might be slow? Is it possible to create a low-level test?)
26 | [ ] security (might use a tool like Zap to walk through the system)
27 | [ ] accessibility
28 | [ ] minum.logging
29 | [ ] graceful degradation
30 | [ ] mobile-first
31 |
32 | white-box testing:
33 | [ ] static analysis considered
34 | [ ] should it be refactored?
35 | [ ] have you done a quick visual scan of the test log output for issues?
36 |
37 | rendered text is highly correct:
38 | [ ] rendered HTML is valid (through a tool like W3C's https://validator.w3.org/)
39 | [ ] dynamic parts are cleaned, e.g. using code like safeAttr(), safeHTML()
40 | [ ] CSS is valid (using a tool like W3C https://jigsaw.w3.org/css-validator/ )
41 |
42 | static values and methods are well-designed.
43 | [] Any new or modified values must be:
44 | * true, literal constants. if any processing is required to build the constant, it's off the
45 | table, except for:
46 | * [null objects](https://en.wikipedia.org/wiki/Null_object_pattern), because the alternative is
47 | purely worse[^1].
48 | * small helper utility methods that require no state - functional-style.
49 | * complex methods should not be static, because I may need to put logging in them, which is state.
50 | * Use a context object to hold items that have broader scope, such as
51 | logging, regular expressions, running threads I'll need to kill, ExecutorService, etc.
52 | * static factory methods are allowed, but they should receive ILogger so we can log.
53 |
54 |
55 |
56 | [^1]: It would require us to do context.emptyObjects().EmptyFoo() instead of Foo.EMPTY, a plainly
57 | worse outcome with minimal benefits.
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | Source directories
2 | ==================
3 |
4 | - main is the production code
5 | - test is the set of tests for the code in main
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/README.md:
--------------------------------------------------------------------------------
1 | Minum source code
2 | ================
3 |
4 | These are the general functionalities provided by Minum. As you
5 | can see here, there is code for a database, for logging, for testing,
6 | and so on.
7 |
8 | There is documentation throughout the project.
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/database/DbException.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.database;
2 |
3 | import java.io.Serial;
4 |
5 | /**
6 | * Exceptions that occur in the {@link Db}
7 | */
8 | public final class DbException extends RuntimeException {
9 |
10 | @Serial
11 | private static final long serialVersionUID = -9063971131447186027L;
12 |
13 | /**
14 | * A {@link RuntimeException} scoped to
15 | * the Minum database package. See {@link RuntimeException#RuntimeException(String)}
16 | */
17 | public DbException(String message) {
18 | super(message);
19 | }
20 |
21 | /**
22 | * A {@link RuntimeException} scoped to
23 | * the Minum database package. See {@link RuntimeException#RuntimeException(String, Throwable)}
24 | */
25 | public DbException(String message, Throwable cause) {
26 | super(message, cause);
27 | }
28 |
29 | /**
30 | * A {@link RuntimeException} scoped to
31 | * the Minum database package. See {@link RuntimeException#RuntimeException(Throwable)}
32 | */
33 | public DbException(Throwable cause) {
34 | super(cause);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/database/README.md:
--------------------------------------------------------------------------------
1 | Database
2 | ========
3 |
4 | A minimalist database. Strongly-typed in-memory collections, backed by eventually-synchronized
5 | disk persistence. See [Database JavaDocs](https://renomad.com/javadoc/com/renomad/minum/database/package-summary.html)
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/database/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains classes for data persistence capabilities.
3 | *
4 | *
5 | * {@code
6 | * //------------------------
7 | * // Define the data type
8 | * //------------------------
9 | *
10 | * public class PersonName extends DbData
59 | */
60 | package com.renomad.minum.database;
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/htmlparsing/ParseNodeType.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.htmlparsing;
2 |
3 | /**
4 | * The different kinds of things in an HTML document.
5 | */
6 | public enum ParseNodeType {
7 | /**
8 | * An HTML element.
9 | *
5 | * Here is an example of a test exercising the parser: 6 | *
7 | *{@code 8 | * @Test 9 | * public void test_HtmlParser_Details1() { 10 | * String input = "21 | *
"; 11 | * var expected = List.of( 12 | * new HtmlParseNode( 13 | * ParseNodeType.ELEMENT, 14 | * new TagInfo(TagName.BR, Map.of("foo", "bar")), 15 | * List.of(), 16 | * "")); 17 | * Listresult = new HtmlParser().parse(input); 18 | * assertEquals(expected, result); 19 | * } 20 | * }
22 | * Some of the testing library depends on this framework, such 23 | * as {@link com.renomad.minum.web.FunctionalTesting.TestResponse#searchOne(com.renomad.minum.htmlparsing.TagName, java.util.Map)}. 24 | * This is heavily used in the tests on Minum, as well as being available to applications, such as 25 | * this example from the Memoria project: 26 | *
27 | *{@code 28 | * logger.test("GET the detail view of a person"); 29 | * { 30 | * var response = ft.get("person?id=" + personId); 31 | * assertEquals(response.searchOne(TagName.H2, Map.of("class","lifespan-name")).innerText().trim(), "John Doe"); 32 | * assertEquals(response.searchOne(TagName.SPAN, Map.of("class","lifespan-era")).innerText().trim(), "November 14, 1917 to March 19, 2003"); 33 | * } 34 | * }35 | * @see examples of search utilities applying this code 36 | */ 37 | package com.renomad.minum.htmlparsing; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/logging/ILogger.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.logging; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Logging code interface 7 | */ 8 | public interface ILogger { 9 | 10 | /** 11 | * Logs helpful debugging information 12 | * @param msg a lambda for what is to be logged. example: () -> "Hello" 13 | */ 14 | void logDebug(ThrowingSupplier
19 | * Similar to {@link #logDebug(ThrowingSupplier)} but used 20 | * for code that runs very often, requires extra calculation, or has 21 | * data of large size. 22 | *
23 | *24 | * It is possible to disable trace logs and thus avoid performance impacts unless 25 | * the data is needed for deeper investigation. 26 | *
27 | * @param msg a lambda for what is to be logged. example: () -> "Hello" 28 | */ 29 | void logTrace(ThrowingSupplier40 | * This log type is expected to be printed least-often, and should 41 | * directly relate to a user action. An example would 42 | * be "New user created: alice" 43 | *
44 | * msg a lambda for what is to be logged. example: () -> "Hello" 45 | */ 46 | void logAudit(ThrowingSupplier53 | * The logger has to stand apart from the rest of the system, 54 | * or else we'll have circular dependencies. 55 | *
56 | */ 57 | void stop(); 58 | 59 | /** 60 | * This method can be used to adjust the active log levels, which 61 | * is a mapping of keys of {@link LoggingLevel} to boolean values. 62 | * If the boolean value is true, that level of logging is enabled. 63 | */ 64 | Map6 | * Examples: 7 | *
8 | *9 | * {@code 10 | * this.logger = context.getLogger(); 11 | * 12 | * logger.logDebug(() -> "an empty path was provided to writeString"); 13 | * 14 | * logger.logTrace(() -> String.format("client connected from %s", sw.getRemoteAddrWithPort())); 15 | * 16 | * logger.logAsyncError(() -> String.format("Error while reading file: %s. %s", path, StacktraceUtils.stackTraceToString(e))); 17 | * 18 | * logger.logAudit(() -> String.format("%s has posted a new video, %s, with short description of %s, size of %d", 19 | * authResult.user().getUsername(), 20 | * newFilename, 21 | * shortDescription, 22 | * countOfVideoBytes 23 | * )); 24 | * } 25 | *26 | */ 27 | package com.renomad.minum.logging; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Minum is a web library with all the components needed to build a web application, 3 | * including a web server and a database. 4 | */ 5 | package com.renomad.minum; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/queue/AbstractActionQueue.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.queue; 2 | 3 | import com.renomad.minum.utils.RunnableWithDescription; 4 | import com.renomad.minum.utils.ThrowingRunnable; 5 | 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | 8 | /** 9 | * This class provides the ability to pop items into 10 | * a queue thread-safely and know they'll happen later. 11 | *
12 | * For example, this is helpful for minum.logging, or passing 13 | * functions to a minum.database. It lets us run a bit faster, 14 | * since the I/O actions are happening on a separate 15 | * thread and the only time required is passing the 16 | * function of what we want to run later. 17 | */ 18 | public interface AbstractActionQueue { 19 | 20 | /** 21 | * Start the queue's processing 22 | */ 23 | AbstractActionQueue initialize(); 24 | 25 | /** 26 | * Adds something to the queue to be processed. 27 | *
28 | * An example: 29 | *
30 | *31 | * {@code actionQueue.enqueue("Write person file to disk at " + filePath, () -> { 32 | * Files.writeString(filePath, pf.serialize()); 33 | * });} 34 | *35 | */ 36 | void enqueue(String description, ThrowingRunnable action); 37 | 38 | /** 39 | * Stops the action queue 40 | * @param count how many loops to wait before we crash it closed 41 | * @param sleepTime how long to wait in milliseconds between loops 42 | */ 43 | void stop(int count, int sleepTime); 44 | 45 | /** 46 | * This will prevent any new actions being 47 | * queued (by setting the stop flag to true and thus 48 | * causing an exception to be thrown 49 | * when a call is made to [enqueue]) and will 50 | * block until the queue is empty. 51 | */ 52 | void stop(); 53 | 54 | /** 55 | * Get the {@link java.util.Queue} of data that is supposed to get 56 | * processed. 57 | */ 58 | LinkedBlockingQueue
5 | * This enables programs to be run outside the normal request/response flow. For example, 6 | *
17 | * A major difference between this and alternatives is its paradigm of lightness. Most 18 | * background processors concern themselves with the potential risks - like if power goes out 19 | * during a step, or if a remote endpoint fails, necessitating a retry. If addressing risks 20 | * such as those are prominent in your consideration, it may be worthwhile to use an 21 | * alternative. 22 | *
23 | *24 | * But, if a prudent assessment is taken, in many cases the benefits of lightness and 25 | * minimalism are sufficiently valuable to make the tradeoff worthwhile. Minimalism 26 | * makes the system harder against bugs of all types. Everything has a tradeoff - using 27 | * large complex systems is likelier to cause subtle bugs of all kinds - correctness, 28 | * performance, security. 29 | *
30 | */ 31 | package com.renomad.minum.queue; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/security/ForbiddenUseException.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.security; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * This is thrown when the user action is prevented by a 7 | * restriction we put on the system. 8 | *9 | * For example, no user is allowed to send more than 10 | * Constants.MAX_READ_LINE_SIZE_BYTES to an endpoint. If 11 | * they do, we'll stop reading and throw this exception. 12 | *
13 | */ 14 | public final class ForbiddenUseException extends RuntimeException { 15 | 16 | @Serial 17 | private static final long serialVersionUID = -1588862919515625579L; 18 | 19 | /** 20 | * See {@link ForbiddenUseException} 21 | */ 22 | public ForbiddenUseException(String msg) { 23 | super(msg); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/security/ITheBrig.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.security; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Monitors the inmates who have misbehaved in our system. 7 | *8 | * a client who needs addressing will be stored in a map in this class for as 9 | * long as required. After they have served their time, they 10 | * are released. 11 | *
12 | *13 | * See also {@link UnderInvestigation} 14 | *
15 | */ 16 | public interface ITheBrig { 17 | // Regarding the BusyWait - indeed, we expect that the while loop 18 | // below is an infinite loop unless there's an exception thrown, that's what it is. 19 | ITheBrig initialize(); 20 | 21 | /** 22 | * Kills the infinite loop running inside this class. 23 | */ 24 | void stop(); 25 | 26 | /** 27 | * Put a client in jail for some infraction, for a specified time. 28 | * 29 | * @param clientIdentifier the client's address plus some feature identifier, like 1.2.3.4_too_freq_downloads 30 | * @param sentenceDuration length of stay, in milliseconds 31 | * @return whether we put this client in jail 32 | */ 33 | boolean sendToJail(String clientIdentifier, long sentenceDuration); 34 | 35 | /** 36 | * Return true if a particular client ip address is found 37 | * in the list. 38 | */ 39 | boolean isInJail(String clientIdentifier); 40 | 41 | /** 42 | * Get the current list of ip addresses that have been 43 | * judged as having carried out attacks on the system. 44 | */ 45 | List4 | * In the modern internet/web, sites undergo constant abuse. Scripts 5 | * are run constantly by attackers to seek out security vulnerabilities. 6 | *
7 | *8 | * Many websites mitigate this by hiring services to protect themselves, placing 9 | * themselves in a more protected position one step back from the full internet. 10 | *
11 | *12 | * This system was designed to be exposed out to the internet. To avoid some 13 | * of the most obvious attacks, the system looks for patterns indicating as 14 | * much. For example, there is no reason a user of the web application should 15 | * need to access an endpoint called ".env", but many insecure sites will allow 16 | * that file to be read, providing insight to attackers. Thus, attackers will 17 | * often request that file. If we see that request, it is assumed we are 18 | * getting a request from an attacker, and that ip address is put on a blacklist 19 | * for a short time. 20 | *
21 | */ 22 | package com.renomad.minum.security; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/state/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package holds classes that help hold necessary system state. 3 | *4 | * There are just two classes in this package, but they are commonly used 5 | * throughout the Minum application. 6 | *
7 | *8 | * One is {@link com.renomad.minum.state.Constants}, which contains constant 9 | * values that are used in various places. For example, the port that is 10 | * opened for secure endpoints. 11 | *
12 | *13 | * Each value has a corresponding entry in the minum.config file, allowing 14 | * users to adjust parameters without needing to recompile. 15 | *
16 | *17 | * The second class in this package is {@link com.renomad.minum.state.Context}, which 18 | * holds a reference to Constants and several other widely-needed items, so that 19 | * code in later parts of the call tree have access. 20 | *
21 | */ 22 | package com.renomad.minum.state; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/templating/README.md: -------------------------------------------------------------------------------- 1 | Templating 2 | ========== 3 | 4 | See package-info.java for more detail on this package. -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/templating/RenderingResult.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.templating; 2 | 3 | /** 4 | * The result of rendering one of the {@link TemplateSection}s, used to build 5 | * up the full template. This is needed so we obtain information about whether our 6 | * user-supplied keys were applied. 7 | * @param renderedSection The result of rendering this section. In the case of a 8 | * {@link TemplateSection} that takes a key, this will be 9 | * the result of replacing that with what the user provided. 10 | * @param appliedKey In cases where a key was replaced with a value supplied by 11 | * the user, this will supply the key that was replaced. This is 12 | * useful to track how the supplied keys are being used in the template. 13 | */ 14 | public record RenderingResult(String renderedSection, String appliedKey) { } 15 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/templating/TemplateParseException.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.templating; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Thrown when failing to parse something in a template 7 | */ 8 | public final class TemplateParseException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -8784893791425360686L; 12 | 13 | public TemplateParseException(String msg) { 14 | super(msg); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/templating/TemplateRenderException.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.templating; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * This exception is thrown when we try to render a string 7 | * template but fail to include a key for one of the key 8 | * values - that is, if the template is "hello {foo}", and 9 | * our map doesn't include a value for foo, this exception 10 | * will get thrown. 11 | */ 12 | public final class TemplateRenderException extends RuntimeException { 13 | 14 | @Serial 15 | private static final long serialVersionUID = -6403838479988560085L; 16 | 17 | public TemplateRenderException(String msg) { 18 | super(msg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/templating/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Text templating capability. Mostly for HTML but useful 3 | * for any situation requiring substitution inside text. 4 | *5 | * See {@link com.renomad.minum.templating.TemplateProcessor} 6 | *
7 | */ 8 | package com.renomad.minum.templating; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/testing/README.md: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | This package provides a minimal testing framework. -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/testing/RegexUtils.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.testing; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * Handy helpers to make regular expression marginally 8 | * easier / more efficient, etc. 9 | */ 10 | public final class RegexUtils { 11 | 12 | /** 13 | * Helper to find a value in a string using a 14 | * Regex. Note, this is not nearly as performant, since 15 | * each call to this method will compile the regular 16 | * expression. 17 | * @return returns the first match found, or an empty string 18 | */ 19 | public static String find(String regex, String data) { 20 | Pattern pattern = Pattern.compile(regex); 21 | Matcher matcher = pattern.matcher(data); 22 | return matcher.find() ? matcher.group(0) : ""; 23 | } 24 | 25 | private RegexUtils() {} 26 | 27 | /** 28 | * Returns whether the regular expression matched the data 29 | * Note: This is a poor-performance method, mainly used 30 | * as a quick helper where performance concerns don't exist,since 31 | * each call to this method will compile the regular 32 | * expression. 33 | */ 34 | public static boolean isFound(String regex, String data) { 35 | Pattern pattern = Pattern.compile(regex); 36 | Matcher matcher = pattern.matcher(data); 37 | return matcher.find(); 38 | } 39 | 40 | /** 41 | * Find a value by regular expression, for testing 42 | *43 | * A helper method to make things it easier to find a value in a string using a 44 | * Regex. This method is slow, since 45 | * each call will compile the regular 46 | * expression. 47 | *
48 | *49 | * This version is similar to {@link #find(String, String)} except 50 | * that it allows you to specify a match group by name. 51 | * For example, here's a regex with a named match group, 52 | * in this example the name is "namevalue": 53 | *
54 | *55 | *
56 | * {@code "\\bname\\b=\"(?58 | * 59 | *.*?)\""} 57 | *
60 | * Thus, to use it here, you would search like this: 61 | *
62 | *63 | *
64 | * {@code find("\\bname\\b=\"(?66 | * 67 | *.*?)\"", data, "namevalue")} 65 | *
68 | * To summarize: in a regex, you specify a matching group by 69 | * surrounding it with parentheses. To name it, you insert 70 | * right after the opening parenthesis a question mark and 71 | * then a string literal surrounded by angle brackets 72 | *
73 | *Important: the name of the match group must be alphanumeric - do 74 | * not use any special characters or punctuation
75 | * @return returns the first match found, or an empty string 76 | */ 77 | public static String find(String regex, String data, String matchGroupName) { 78 | Pattern pattern = Pattern.compile(regex); 79 | Matcher matcher = pattern.matcher(data); 80 | return matcher.find() ? matcher.group(matchGroupName) : ""; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/testing/StopwatchUtils.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.testing; 2 | 3 | /** 4 | * This class provides some tools for running a virtual stopwatch 5 | * while code is running, to examine code speed. 6 | *11 | {@code 12 | final var timer = new StopWatch().startTimer(); 13 | for (var i = 1; i < 5; i++) { 14 | doStuff(); 15 | } 16 | final var time = timer.stopTimer(); 17 | printf("time taken was " + time " + milliseconds"); 18 | } 19 | *20 | */ 21 | public final class StopwatchUtils { 22 | 23 | private long startTime; 24 | 25 | public StopwatchUtils startTimer() { 26 | this.startTime = System.currentTimeMillis(); 27 | return this; 28 | } 29 | 30 | public StopwatchUtils() { 31 | startTime = 0; 32 | } 33 | 34 | 35 | public long stopTimer() { 36 | final var endTime = System.currentTimeMillis(); 37 | return endTime - startTime; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/testing/TestFailureException.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.testing; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Thrown when a test fails 7 | */ 8 | public final class TestFailureException extends RuntimeException{ 9 | 10 | @Serial 11 | private static final long serialVersionUID = 2937719847418284951L; 12 | 13 | /** 14 | * This constructor allows you to provide a text message 15 | * for insight into what exceptional situation took place. 16 | */ 17 | public TestFailureException(String msg) { 18 | super(msg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/testing/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Automated software testing 3 | */ 4 | package com.renomad.minum.testing; -------------------------------------------------------------------------------- /src/main/java/com/renomad/minum/utils/ByteUtils.java: -------------------------------------------------------------------------------- 1 | package com.renomad.minum.utils; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Handy helpers when working with bytes 7 | */ 8 | public final class ByteUtils { 9 | 10 | private ByteUtils() {} 11 | 12 | /** 13 | * A helper method to reduce some of the boilerplate 14 | * code when converting a list of bytes to an array. 15 | *
16 | * Often, we are gradually building up a list - the list takes 17 | * care of accommodating more elements as necessary. An 18 | * array, in contrast, is just a single size and doesn't 19 | * resize itself. It's much less convenient to use, so we 20 | * more often use lists. 21 | *
22 | */ 23 | public static byte[] byteListToArray(List
12 | * This is a thread-safe data structure.
13 | */
14 | public final class ConcurrentSet
37 | * See docs/http_protocol/password_storage_cheat_sheet
38 | *
10 | * Protects against some common negative cases:
11 | *
20 | * Throws an {@link InvariantException} if false
21 | * @param predicate the boolean expression that must be true at this point
22 | * @param message a message that will be included in the exception if this is false
23 | */
24 | public static void mustBeTrue(boolean predicate, String message) {
25 | if (!predicate) {
26 | throw new InvariantException(message);
27 | }
28 | }
29 |
30 | /**
31 | * Specify something which must be false
32 | *
33 | * Throws an {@link InvariantException} if true
34 | * @param predicate the boolean expression that must be false at this point
35 | * @param message a message that will be included in the exception if this is true
36 | */
37 | public static void mustBeFalse(boolean predicate, String message) {
38 | if (predicate) {
39 | throw new InvariantException(message);
40 | }
41 | }
42 |
43 | /**
44 | * specifies that the parameter must be not null.
45 | *
46 | * Throws an {@link InvariantException} if null.
47 | * @return the object if not null
48 | */
49 | public static
45 | *
4 | * Here is a typical "main" method for an application. The important thing to note is we are initializing {@link com.renomad.minum.web.FullSystem} and
5 | * using it to register endpoints. A more organized approach is to put the endpoint registrations
6 | * into another file. See the example project in the Minum codebase or any of the other example projects.
7 | * Hi there world!
28 | * Here's an example of a business-related function using authentication and the Minum database. This
29 | * code is extracted from the SampleDomain.java file in the src/test directory:
30 | *
13 | *
16 | */
17 | byte[] readFile(String path) throws IOException;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/utils/InvariantException.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.utils;
2 |
3 | /**
4 | * An exception specific to our invariants. See {@link Invariants}
5 | */
6 | @SuppressWarnings("serial")
7 | public final class InvariantException extends RuntimeException {
8 | public InvariantException(String msg) {
9 | super(msg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/utils/Invariants.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.utils;
2 |
3 | /**
4 | * Utilities for asserting invariants within the code.
5 | *
6 | * The purpose here is to make firm statements about the code. This is
7 | * to help document the code, (e.g. at this point, x is the sum of the ...),
8 | * to include testing mindset in the code, and to guard against adding
9 | * bugs during maintenance.
10 | */
11 | public final class Invariants {
12 |
13 | private Invariants() {
14 | // cannot construct
15 | }
16 |
17 | /**
18 | * Specify something which must be true.
19 | *
36 | * Make sure, when using this, to assign it to a fully-defined
37 | * type, e.g. {@code Map
50 | * Make sure, when using this, to assign it to a fully-defined
51 | * type, e.g. {@code Map
38 | * The values will be pre-filtered to skip any null values.
39 | *
40 | * @param alternate a {@link Callable} that will be run when no elements were found.
41 | * @param searchPredicate a {@link Predicate} run to search for an element in the stream.
42 | * @throws InvariantException if there are two or more results found
43 | */
44 | public static
10 | * --i_am_a_boundary
11 | * Content-Type: text/plain
12 | * Content-Disposition: form-data; name="text1"
13 | *
14 | * I am a value that is text
15 | * --i_am_a_boundary
16 | * Content-Type: application/octet-stream
17 | * Content-Disposition: form-data; name="image_uploads"; filename="photo_preview.jpg"
18 | *
19 | *
20 | *
21 | * In this example, there are two partitions, and each has a Content-Disposition header.
22 | * The first has a name of "text1" and the second has a name of "image_uploads". The
23 | * second partition also has a filename.
24 | *
25 | * This is useful for filtering partition data when an endpoint receives multipart data.
26 | *
27 | */
28 | public final class ContentDisposition {
29 | private final String name;
30 | private final String filename;
31 |
32 | public ContentDisposition(String name, String filename) {
33 |
34 | this.name = name;
35 | this.filename = filename;
36 | }
37 |
38 | public String getName() {
39 | return name;
40 | }
41 |
42 | public String getFilename() {
43 | return filename;
44 | }
45 |
46 | @Override
47 | public boolean equals(Object o) {
48 | if (this == o) return true;
49 | if (o == null || getClass() != o.getClass()) return false;
50 | ContentDisposition that = (ContentDisposition) o;
51 | return Objects.equals(name, that.name) && Objects.equals(filename, that.filename);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hash(name, filename);
57 | }
58 |
59 | @Override
60 | public String toString() {
61 | return "ContentDisposition{" +
62 | "name='" + name + '\'' +
63 | ", filename='" + filename + '\'' +
64 | '}';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/CountBytesRead.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | class CountBytesRead{
4 | private int count;
5 | public void increment() {count += 1;}
6 |
7 | public void incrementBy(int i) {
8 | count += i;
9 | }
10 |
11 | public int getCount() {
12 | return count;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/HttpServerType.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | /**
4 | * An enum to represent the mode of conversation for HTTP -
5 | * plain text or encrypted (TLS)
6 | */
7 | public enum HttpServerType {
8 | /**
9 | * Represents a plain text, non-encrypted HTTP conversation
10 | */
11 | PLAIN_TEXT_HTTP,
12 |
13 | /**
14 | * Represents an HTTPS encrypted TLS conversation
15 | */
16 | ENCRYPTED_HTTP,
17 |
18 | /**
19 | * Indicates an unknown state
20 | */
21 | UNKNOWN,
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/HttpVersion.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | /**
4 | * The HTTP versions we handle
5 | */
6 | public enum HttpVersion {
7 | ONE_DOT_ZERO, ONE_DOT_ONE, NONE
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/IInputStreamUtils.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | /**
7 | * An interface for the {@link InputStreamUtils} implementation.
8 | * Solely created to provide better testing access
9 | */
10 | interface IInputStreamUtils {
11 |
12 | /**
13 | * Reads a line of text, stopping when reading a newline.
14 | * Skips over carriage returns, so we read a HTTP_CRLF properly.
15 | *
16 | * If the stream ends, return what we have so far.
17 | */
18 | String readLine(InputStream inputStream) throws IOException;
19 |
20 | /**
21 | * Reads "lengthToRead" bytes from the input stream
22 | */
23 | byte[] read(int lengthToRead, InputStream inputStream);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/IRequest.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | /**
4 | * An interface for {@link Request}. Built
5 | * to enable easier testing on web handlers.
6 | *
7 | */
8 | public interface IRequest {
9 |
10 | Headers getHeaders();
11 |
12 | /**
13 | * Obtain information about the first line. An example
14 | * of a RequestLine is: {@code GET /foo?bar=baz HTTP/1.1}
15 | */
16 | RequestLine getRequestLine();
17 |
18 | /**
19 | * This getter will process the body data fully on the first
20 | * call, and cache that data for subsequent calls.
21 | *
22 | * If there is a need to deal with very large data, such as
23 | * large images or videos, consider using {@link #getSocketWrapper()}
24 | * instead, which will allow fine-grained control over pulling
25 | * the bytes off the socket.
26 | *
27 | * For instance, if expecting a video (a large file), it may be prudent to store
28 | * the data into a file while it downloads, so that the server
29 | * does not need to hold the entire file in memory.
30 | */
31 | Body getBody();
32 |
33 | /**
34 | * Gets a string of the ip address of the client sending this
35 | * request. For example, "123.123.123.123"
36 | */
37 | String getRemoteRequester();
38 |
39 | /**
40 | * This getter is expected to be used for situations required finer-grained
41 | * control over the socket, such as when dealing with streaming input like a game or chat,
42 | * or receiving a very large file like a video. This will enable the user to
43 | * read and send on the socket - powerful, but requires care.
44 | *
46 | * Note: This is an unusual method.
47 | *
48 | * It is an error to call this in addition to {@link #getBody()}. Use one or the other.
49 | * Mostly, expect to use getBody unless you really know what you are doing, such as
50 | * streaming situations or custom body protocols.
51 | *
61 | * If this extra level of control is not needed, the developer would benefit more from
62 | * using the {@link #getBody()} method, which is far more convenient.
63 | */
64 | Iterable
72 | * If this extra level of control is not needed, the developer would benefit more from
73 | * using the {@link #getBody()} method, which is far more convenient.
74 | */
75 | Iterable
13 | * It is valuable to get the previously-calculated response data, in case there is
14 | * something useful - like valuable error messages.
15 | */
16 | public record LastMinuteHandlerInputs(IRequest request, IResponse response){}
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/Partition.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | import com.renomad.minum.utils.StringUtils;
4 |
5 | import java.util.Arrays;
6 | import java.util.Objects;
7 |
8 | /**
9 | * Represents a single partition in a multipart/form-data body response
10 | */
11 | public final class Partition {
12 |
13 | private final Headers headers;
14 | private final byte[] content;
15 | private final ContentDisposition contentDisposition;
16 |
17 | public Partition(Headers headers, byte[] content, ContentDisposition contentDisposition) {
18 | this.headers = headers;
19 | this.content = content;
20 | this.contentDisposition = contentDisposition;
21 | }
22 |
23 | public Headers getHeaders() {
24 | return headers;
25 | }
26 |
27 | public ContentDisposition getContentDisposition() {
28 | return contentDisposition;
29 | }
30 |
31 | public byte[] getContent() {
32 | return content.clone();
33 | }
34 | public String getContentAsString() {
35 | return StringUtils.byteArrayToString(content);
36 | }
37 |
38 | @Override
39 | public boolean equals(Object o) {
40 | if (this == o) return true;
41 | if (o == null || getClass() != o.getClass()) return false;
42 | Partition partition = (Partition) o;
43 | return Objects.equals(headers, partition.headers) && Arrays.equals(content, partition.content) && Objects.equals(contentDisposition, partition.contentDisposition);
44 | }
45 |
46 | @Override
47 | public int hashCode() {
48 | int result = Objects.hash(headers, contentDisposition);
49 | result = 31 * result + Arrays.hashCode(content);
50 | return result;
51 | }
52 |
53 | @Override
54 | public String toString() {
55 | return "Partition{" +
56 | "headers=" + headers +
57 | ", contentDisposition=" + contentDisposition +
58 | '}';
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/renomad/minum/web/PreHandlerInputs.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.web;
2 |
3 | /**
4 | * The input parameters that are required to add a pre-handler. See {@link WebFramework#registerPreHandler(ThrowingFunction)}
5 | * @param clientRequest the raw {@link Request} from the user
6 | * @param endpoint the endpoint that was properly chosen for the combination
7 | * of path and verb.
8 | */
9 | public record PreHandlerInputs(IRequest clientRequest, ThrowingFunction
7 | * Exceptions stop bubbling up at the thread invocation. If we
8 | * don't take care to deal with that in some way, we can easily
9 | * just lose the information. Something could be badly broken and
10 | * we could be totally oblivious to it. This interface is to
11 | * alleviate that situation.
12 | */
13 | @FunctionalInterface
14 | public interface ThrowingConsumer
7 | * exceptions stop bubbling up at the thread invocation. If we
8 | * don't take care to deal with that in some way, we can easily
9 | * just lose the information. Something could be badly broken and
10 | * we could be totally oblivious to it. This interface is to
11 | * alleviate that situation.
12 | */
13 | @FunctionalInterface
14 | public interface ThrowingFunction
9 | * {@code
10 | * package org.example;
11 | *
12 | * import com.renomad.minum.web.FullSystem;
13 | * import com.renomad.minum.web.Response;
14 | *
15 | * import static com.renomad.minum.web.RequestLine.Method.GET;
16 | *
17 | * public class Main {
18 | *
19 | * public static void main(String[] args) {
20 | * FullSystem fs = FullSystem.initialize();
21 | * fs.getWebFramework().registerPath(GET, "", request -> Response.htmlOk("
27 | * {@code
32 | * public IResponse formEntry(IRequest r) {
33 | * final var authResult = auth.processAuth(r);
34 | * if (! authResult.isAuthenticated()) {
35 | * return Response.buildLeanResponse(CODE_401_UNAUTHORIZED);
36 | * }
37 | * final String names = db
38 | * .values().stream().sorted(Comparator.comparingLong(PersonName::getIndex))
39 | * .map(x -> "
4 | *
5 | * authentication: do we know who you are?
6 | * authorization: are you allowed to access (data/page/whatever)?
7 | *
8 | */
9 | package com.renomad.minum.sampledomain.auth;
--------------------------------------------------------------------------------
/src/test/java/com/renomad/minum/sampledomain/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This minum.sampledomain is here as the standard template for how
3 | * to build any domain-oriented code. Here, domain means
4 | * your need - are you building a forum? A game? Whatever it
5 | * is that relies on the underlying web framework to give
6 | * it access to web requests and responses.
7 | */
8 | package com.renomad.minum.sampledomain;
--------------------------------------------------------------------------------
/src/test/java/com/renomad/minum/sampledomain/photo/Photograph.java:
--------------------------------------------------------------------------------
1 | package com.renomad.minum.sampledomain.photo;
2 |
3 | import com.renomad.minum.database.DbData;
4 |
5 | import static com.renomad.minum.utils.SerializationUtils.deserializeHelper;
6 | import static com.renomad.minum.utils.SerializationUtils.serializeHelper;
7 |
8 | public class Photograph extends DbData