---
title: 'Embedded Server and Request Processing Model'
source: 'https://academia.sh/en/courses/enterprise-application-framework/embedded-server-and-request-model'
course: 'The Enterprise Application Framework'
language: en
updated: '2026-09-13T05:08:57+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Splits the request pipeline's links into three sources: the container's default, an annotation, an explicit call"
    - 'Shows, by running it, that the run order the container produces does not change when the same set of links is declared in a different order'
    - 'Measures that an annotation has no effect at all when a class carrying it is called by hand outside the container'
---

# Embedded Server and Request Processing Model

The links of the request pipeline split into three sources: the object container's own default, an annotation, and an explicit call. When the same set of links is declared in a different order, the run order the container produces does not change; when a class carrying an annotation is called by hand outside the container, the annotation has no effect at all.

The previous topic's last lesson showed that behavior could be added without ever touching
the call site, and that the limit of this was wherever the proxy could reach. This topic
follows the same container with one question: while the container is handling a request,
what share of it comes from each of the same three sources — call, annotation, default?
Endpoints, validation, and data access all arise from the same three sources too; this
lesson starts from the outermost layer of the chain and counts which link a request passes
through from the moment it enters the container to the moment it leaves.

The server is modeled here. No real socket is opened, no real port is listened on, no real
byte is read from the network — the path a request follows while passing through the
container is built as a set of small function objects that run in sequence over a string.
This simplification does not change the lesson's question: in a real embedded server too, a
request passes through a fixed sequence of links from the listening socket to the
application code, and the question of how many links in that sequence have a counterpart in
the source stays the same whether the socket is real or not. The server's own job is to
accept the connection and put the request into this chain — the Server-Side Fundamentals
course established this, and the chain's general idea, as a concept, and it is not repeated
here. The course's own term, request pipeline, is kept separate from the same course's term
middleware chain: a middleware chain is a sequence the caller lines up by hand, while a
request pipeline, as this lesson will show, is a sequence the container builds according to
its own rule.

There is another reason for modeling the server, one tied to the measurement itself: if a
real port were opened, which port is free would vary by machine, and the ban on
environment-dependent data that holds across this whole lesson series already rules out
writing such a value. The string-based model removes this problem from the start — the odds
of the same program giving two different outputs on two different machines drop to zero,
because what is measured is not a delay on the network or a port number, but the function
chain the container applies in a fixed order. In a real embedded server, the outermost link
of this chain is reading from and writing to the network; here that link is skipped, and the
chain starts directly from the first point the application code sees — the request itself.

## Three Sources for the Links

- **WD1.** The request is modeled here as a string (`body`); every link adds its own
  signature to this string; reading from and writing to the network are not modeled.
- **WD2.** Discovering annotated classes does not perform a real classpath scan; the
  container goes through the candidate class list given to the `scan` call. This is a
  modeled counterpart of a real framework's classpath-walking scan.

A link in the chain can exist through one of three routes. A link written into the
container's own body, always running without putting any annotation on any class, is a
default — here the two links that add an identifier to the request and catch errors at the
end of the chain fall into this group; both are written inside the `Container` class's own
`run` method and need no outside call. A `@Link` annotation placed on a class **requests**
that the container add it to the chain once it recognizes that class; this request does
nothing unless the container's `scan` method is given a candidate list. The third route is
the barest of the three: the code itself puts a link directly into the chain with a
`container.add(...)` call — the exact line in the source that gives rise to it is this very
line.

```java
// Container.java — who added each request-pipeline link: the caller, an annotation, or the container's own default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Link { int priority() default 100; }

    interface LinkFunction { String apply(String body, List<String> trace); }

    final List<Class<?>> annotatedClasses = new ArrayList<>();
    final List<LinkFunction> calledLinks = new ArrayList<>();
    final List<String> calledNames = new ArrayList<>();

    void scan(Class<?>... candidates) {
        for (Class<?> candidate : candidates)
            if (candidate.isAnnotationPresent(Link.class)) annotatedClasses.add(candidate);
    }

    void add(String name, LinkFunction l) {
        calledNames.add(name);
        calledLinks.add(l);
    }

    String run(String body, List<String> trace) {
        try {
            String s = body;
            trace.add("request-id(default)");

            List<Class<?>> sortedAnnotated = new ArrayList<>(annotatedClasses);
            sortedAnnotated.sort(Comparator.comparingInt(c -> c.getAnnotation(Link.class).priority()));
            for (Class<?> c : sortedAnnotated) {
                LinkFunction l = (LinkFunction) c.getDeclaredConstructor().newInstance();
                trace.add(c.getSimpleName() + "(annotation)");
                s = l.apply(s, trace);
            }
            for (int i = 0; i < calledLinks.size(); i++) {
                trace.add(calledNames.get(i) + "(call)");
                s = calledLinks.get(i).apply(s, trace);
            }
            trace.add("error-handling(default)");
            return s;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Container c1 = new Container();
        c1.scan(LoggingLink.class, CompressionLink.class);
        c1.add("add-header", (b, t) -> b + "+header");
        c1.add("write-length", (b, t) -> b + "+length");
        List<String> t1 = new ArrayList<>();
        String r1 = c1.run("request", t1);

        Container c2 = new Container();
        c2.scan(CompressionLink.class, LoggingLink.class);
        c2.add("add-header", (b, t) -> b + "+header");
        c2.add("write-length", (b, t) -> b + "+length");
        List<String> t2 = new ArrayList<>();
        String r2 = c2.run("request", t2);

        Container c3 = new Container();
        c3.scan(LoggingLink.class, CompressionLink.class);
        c3.add("write-length", (b, t) -> b + "+length");
        c3.add("add-header", (b, t) -> b + "+header");
        List<String> t3 = new ArrayList<>();
        String r3 = c3.run("request", t3);

        System.out.println("-- setup-1: L,C scanned; header then length added --");
        t1.forEach(x -> System.out.println("  " + x));
        System.out.println("-- setup-2: C,L scanned (candidate order reversed); header then length added --");
        t2.forEach(x -> System.out.println("  " + x));
        System.out.println("-- setup-3: L,C scanned; length then header added (call order reversed) --");
        t3.forEach(x -> System.out.println("  " + x));

        System.out.println("setup-1 vs setup-2 order the same (annotated part): " + t1.equals(t2));
        System.out.println("setup-1 vs setup-3 order the same (call part included): " + t1.equals(t3));
        System.out.println("body results the same (1 and 2): " + r1.equals(r2));

        int byDefault = 0, byAnnotation = 0, byCall = 0;
        for (String line : t1) {
            if (line.contains("(default)")) byDefault++;
            else if (line.contains("(annotation)")) byAnnotation++;
            else if (line.contains("(call)")) byCall++;
        }
        System.out.printf("chain: %d default, %d annotation, %d call, %d links total%n",
                byDefault, byAnnotation, byCall, byDefault + byAnnotation + byCall);

        System.out.println("-- manually built chain: container never involved --");
        List<String> manualTrace = new ArrayList<>();
        CompressionLink comp1 = new CompressionLink();
        LoggingLink log1 = new LoggingLink();
        String manualBody = "request";
        manualTrace.add("CompressionLink(manual)");
        manualBody = comp1.apply(manualBody, manualTrace);
        manualTrace.add("LoggingLink(manual)");
        manualBody = log1.apply(manualBody, manualTrace);
        manualTrace.forEach(x -> System.out.println("  " + x));
        System.out.println("default links in manual setup: "
                + manualTrace.stream().filter(x -> x.contains("default")).count());
        System.out.println("first class called in manual setup is Compression (priority 20 in container, should come after Logging): "
                + manualTrace.get(0).startsWith("Compression"));
        System.out.println("defect type: ineffective (no exception, @Link.priority value never read, order unchanged)");
    }
}

@Container.Link(priority = 10)
class LoggingLink implements Container.LinkFunction {
    public String apply(String body, List<String> trace) { return body + "+log"; }
}

@Container.Link(priority = 20)
class CompressionLink implements Container.LinkFunction {
    public String apply(String body, List<String> trace) { return body + "+compress"; }
}
```

```
-- setup-1: L,C scanned; header then length added --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  add-header(call)
  write-length(call)
  error-handling(default)
-- setup-2: C,L scanned (candidate order reversed); header then length added --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  add-header(call)
  write-length(call)
  error-handling(default)
-- setup-3: L,C scanned; length then header added (call order reversed) --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  write-length(call)
  add-header(call)
  error-handling(default)
setup-1 vs setup-2 order the same (annotated part): true
setup-1 vs setup-3 order the same (call part included): false
body results the same (1 and 2): true
chain: 2 default, 2 annotation, 2 call, 6 links total
-- manually built chain: container never involved --
  CompressionLink(manual)
  LoggingLink(manual)
default links in manual setup: 0
first class called in manual setup is Compression (priority 20 in container, should come after Logging): true
defect type: ineffective (no exception, @Link.priority value never read, order unchanged)
```

The total six links split into three sources: `request-id` and `error-handling` are
defaults — behavior the `Container` class writes into its own body without needing any
outside input. `LoggingLink` and `CompressionLink` are annotations — there is no line
anywhere in the source that calls these two classes, only a `@Link` mark on top of them and
a `scan` call that reads that mark. `add-header` and `write-length` are calls — without a
`container.add(...)` line, neither of these two links would ever exist in the chain. The six
links split evenly across the three sources — two apiece — but this evenness is not a rule,
it is a property of the example this lesson chose; the shared definition's twelve behaviors
did not split evenly between call, annotation, and default — annotation dominated. The
question the course asks is the same here too: this time what is measured is not a
component's construction, but the path a request follows while passing through a container.

Someone reading the source text top to bottom sees only two of these six links directly. The
lines `container.add("add-header", ...)` and `container.add("write-length", ...)` are right
there, and a reader can infer from the source that these two links exist and in what order
they were added. The remaining four leave their trace somewhere else in the source: for
`LoggingLink` and `CompressionLink` the only trace is the `@Link` line above the class
definition; for `request-id` and `error-handling` there is no trace at all — these two can
only be found by reading the body of the `Container.run` method, and that method sits in a
file the calling code never sees. Someone reading the source only from the caller's own file
misses where two-thirds of the chain comes from.

## The Container Applies Its Own Order

- **WD3.** The annotated link with the smaller `priority` value runs first; this is the
  container's own rule and is written nowhere in the source.

The only difference between setup-1 and setup-2 is the order of the classes given to the
`scan` call: one scans the logging class first, then the compression class; the other does
the opposite. Even though the source text is written differently, the trace list comes out
byte-for-byte identical, because the container re-sorts the scanned classes by its own
`priority` field and never uses the order of the array given to `scan`. **The run order of
the links is not the order the caller wrote in the source; the container applies its own
order, and two different orderings in the source produce the same run order.**

But this rule only holds for links found through an annotation. Setup-3 uses the same scan
order as setup-1; its only difference is the order of the two `add` calls: length first,
then header. The trace list comes out different this time — `write-length` appears first,
`add-header` second. The container applies its own ordering rule to the links it finds
through annotations, but it does no ordering at all for links added through an explicit
`add` call; it puts them into the chain in exactly the order they were called. The ordering
rule applies only to what the container **discovers**, not to what it is **told** — an
annotation is a request left to the container's own decision, while a call is the caller's
own decision, and the container does not change it.

This distinction has a practical consequence: if two developers give the same two annotated
classes to `scan` in a different order — one collected the files with a tool that lists them
alphabetically, the other with one that lists them by modification date — their source texts
look different, but the chain produced is byte-for-byte the same. The order does not live in
the source, it lives in the `priority` field; someone doing a code review has to look at the
annotation types' own definitions, not the order in the `scan` call, to see that both
versions produce the same behavior. There is no such guarantee for an explicit call —
changing the order of the `add` calls also changes the order of the chain, because the
container applies no rule there at all.

## In a Manually Built Chain, the Annotation Has No Effect at All

- **WD4.** In the manually built call, `CompressionLink` and `LoggingLink` are instantiated
  directly and their `apply` methods are called in sequence; no `Container` object is
  created at all.
- **WD5.** The `@Link` annotation keeps standing on both classes; it was not removed, not
  deleted. The reason it has no effect in the manual setup is not that the annotation
  disappeared, but that there is no party left to read it.

The last measurement takes the container completely out of the picture: `CompressionLink`
and `LoggingLink` are constructed by hand with `new`, and their `apply` methods are called
by hand, in sequence. Two results stand out. First, there is no line tagged `default` in the
trace list at all — `request-id` and `error-handling` are written into the body of the
`Container.run` method, and since that method is never called, neither behavior ever
happens. Second, `CompressionLink` is called first and `LoggingLink` second — even though
their order inside the container was exactly the reverse, since `CompressionLink`'s
`priority` is 20 and `LoggingLink`'s is 10. In the manual call, this number is never read at
all; the `@Link(priority = 20)` annotation sits on the class, but there is no `scan` call, no
sorting step, left to read it. An annotation is not a property but a request; if there is no
container to fulfill the request, both classes are called exactly like two ordinary Java
objects, and the order stays entirely in the caller's hands.

This completes the three-source distinction from the start of the lesson. A default
behavior is part of the container's own code, so it does not exist without a
container; an annotation carries a request, so it is ineffective without a container to read
it; only a call — a method invoked directly — produces the same behavior whether or not a
container exists, because a call is already an expression written in the source. The
difference these three behaviors show inside and outside the container reveals what the
container actually adds: it does not add a new capability, it reads and applies two kinds of
request (default and annotation) that are never written in the source.

This result carries a practical warning for whoever tests it: calling the `LoggingLink` or
`CompressionLink` class in a unit test outside the container confirms that class's own
logic, but never tests its place in the chain, its order, or the default links that run
alongside it. Two measurements answer two separate questions: is the class's own behavior
correct, and does the container put it in the correct place. The first question can be
answered with a manually built object; the second can only be answered by running the
container itself — and that is exactly what setup-1, setup-2, and setup-3 do together in
this lesson.

## Summary

- The request pipeline's six links split into three sources: two are the container's
  default, two are an annotation, two are an explicit call.
- The run order of links found through an annotation is decided by the container's own
  `priority` rule and is independent of the scan order in the source; two different scan
  orders produced the same result.
- The order of links added through an explicit call plays no part in the container's rule;
  the order the caller wrote is kept exactly.
- With the container taken out of the picture and the same classes called by hand, the
  default links never ran and the annotation's `priority` value was never read; without a
  party to read it, the annotation was ineffective.
- The body result the two setups produced came out byte-for-byte identical, as long as the
  order stayed the same.

## Next Step

This lesson measured the chain a request follows while passing through the container, but
it never settled which job the request is met with at the end of the chain — the links only
changed the request, they never routed it. The next lesson closes that gap: it measures
which annotation, not which expression, declares an endpoint in the source, and what the
container answers when a request does not match any of them.
