# Working with HTTP sources

> An overview of how to connect data sources to Orbital

Source: https://orbitalhq.com/docs/describing-data-sources/http

**Note: See also: OpenAPI**
Orbital has first-class support for working with [OpenAPI specs](/docs/describing-data-sources/open-api) and [SOAP WSDLs](/docs/describing-data-sources/soap)  The following guide is intended for consuming HTTP services without a separate spec.
 

## Describing HTTP with Taxi
If you're not working with other HTTP specs, you can describe your services directly using Taxi. Taxi is very rich and expressive, and
is designed to fully support describing API sources, for teams who are not currently using an existing standard.

## Services

A service is simply a group of operations.

```taxi
service PeopleService {
   operation listAllPeople():Person[]
}
```

Services that contain HTTP operations may optionally declare an `@HttpService` annotation, which allows it to set a baseUrl for all contained operations.

```taxi
@HttpService(baseUrl="https://foo.com")
service PeopleService {
   // resolves to https://foo.com/people
   @HttpOperation(method = 'GET', url = '/people')
   operation listAllPeople():Person[]
}
```

## Http Operations

An operation defines a function on the API.

```taxi
@HttpOperation(method = 'GET', url = 'https://myservice/people')
operation listAllPeople():Person[]
```

Operations often have annotations that provide hints to tooling as to how to invoke them.

Taxi ships with some standard annotations, as part of it's [std-lib](https://gitlab.com/taxi-lang/taxi-lang/tree/master/taxi-stdlib-annotations/src/main/java/lang/taxi/annotations).  Although it's up to individual tooling to determine how to interpret these, the suggested usage is as follows:

| Annotation                        | Usage                                                                                               |
|:----------------------------------|:----------------------------------------------------------------------------------------------------|
| `@HttpOperation(method,url)`      | Indicates that the operation should be invoked over HTTP, using the provided `method` and `url`     |
| `@HttpRequestBody`                | Indicates that a parameter will be found on the request body                                        |
| `@PathVariable(name)`             | Indicates a property from the path that should be populated with a variable passed to the operation |
| `@HttpHeader(name,value, prefix)` | Defines how header are passed. See below                                                            |

It's possible to use environment variables in these annotations, as described [here](/docs/deploying/configuring-orbital#environment-variables-in-annotations).

### Http Headers

Http headers are passed as annotations, which follow the following format:

```taxi
annotation HttpHeader {
   name : String
   [[ Pass a value when using as an annotation on an operation. For parameters, it's valid to allow the value to be populated from the parameter. ]]
   value : String?
   prefix : String?
   suffix : String?
}
```

Http Headers can be defined in operations in a few different ways:

#### As a fixed value, on an operation
```taxi
service MyService {
  @HttpOperation(method = "GET", url = "...")
  @HttpHeader(name = "Consumes", value = "application/json")
  operation findPeople():Person[]
}
```

#### As an input into an operation
```taxi
service MyService {
  operation findPeople(
     @HttpHeader(name = "Cache-Control", prefix = "max-age=") cacheMaxAge : CacheMaxAge
  ):Person[]
}
```

Which is then invoked as:

```taxi
given { maxAge : CacheMaxAge = 64000 } 
find { Person[] }
```

Additionally, header values can be calculated, using expressions:

```taxi
service MyService {
  operation findPeople(
     @HttpHeader(name = "If-Modified-Since") ifModifiedSince : Instant = addDays(now(),-7)
  ):Person[]
}
```

### Path variables

**Note: Be sure to use a semantic type**
All variables in HTTP requests must have a semantic type. Trying to declare a variable as `String` or `Int` will result in the variable not being populated, as they can't be matched


```taxi
>    import taxi.http.PathVariable
     service PetApi {
>       @HttpOperation(method = "GET", url = "https://pets.com/pets/{petId}")
        operation getPetById(@PathVariable("petId") id: PetId): Pet
     }
```

The name on the annotation `@PathVariable("petId")` must match the placeholder in the url (`/pets/{petId}`)

Then a query such as:

```taxi
given { pet: PetId = "123" }
find { Pet }
```

Alternatively, using a constraint:

```taxi
find { Pet(PetId == "123") }
```

Would result in an HTTP GET to `https://pets.com/pets/123`

### Query parameters
Query parameters (sometimes know as Query Strings) can be populated into a URL using  the `taxi.http.QueryVariable` annotation:

```taxi
>   import taxi.http.QueryVariable
    
    service PersonApi {
      @HttpOperation(method = "GET", url = "https://people.com/people")
      operation listPeople(
>          @QueryVariable("apiKey") key: ApiKey
      ): Person[]
    }
```

Then a query such as:

```taxi
given { key: ApiKey = "123" }
find { Person[] }
```

Would result in an HTTP GET to `https://people.com/people?apiKey=123`

The name in the annotation defines the parameter that is passed on the query string

#### Optional params
Query params can be optional, by making the **type** nullable - eg: `ApiKey?`:

```taxi
    import taxi.http.QueryVariable
    
    service PersonApi {
      @HttpOperation(method = "GET", url = "https://people.com/people")
      operation listPeople(
>        @QueryVariable("apiKey") apiKey: ApiKey?
      ): Person[]
    }
```

### Examples

```taxi
service MovieService {
   @HttpOperation(method = "GET" , url = "https://myMovies/movies")
   operation findAllMovies() : Movie[]

   @HttpOperation(method = "GET" , url = "https://myMovies/movies/{id}")
   operationFindMovie( @PathVariable(name = "id") id : MovieId ) : Movie
}

@HttpService(baseUrl = "http://actorService")
service ActorService {
   // resolves to http://actorService/actors
   @HttpOperation(method = "GET" , url = "/actors")
   operation findAllActors() : Actor[]
}
```

Names of operation parameters are optional.  This is to encourage developers to leverage a richer type system where possible:

```taxi
// These two declarations are both valid, and describe the same operation
operation convertUnits(source:Weight, targetUnit:Unit):Weight
operation convertUnits(Weight,Unit):Weight
```

## Operation Contracts & Constraints

Contracts and constraints are useful for telling tooling about what functionality an operation can provide, and what conditions must be met before invoking.

Both contracts and constraints use the same syntax.

```taxi
type Money {
   currency : String
   amount : Decimal
}
operation convertCurrency(input: Money,
      targetCurrency: String) : Money(from input, currency = targetCurrency)
```

### from input

A contract may indicate that a return type is derived from one of the inputs, by using the `from {input}` syntax:

```taxi
operation convertUnits(input: Weight, target: Unit):Weight( from input )
```

### Attribute constraints

Attribute constraints describe either a pre-condition \(if on an input\) or a post-condition \(if on a return type\) for an operation

```taxi
operation convertFromPounds(input : Money(currency = 'GBP'), target: Currency)
    : Money( from input, currency = target)
```

As shown above, attribute constraints may either be:

* A constant value \(ie., `"GBP"`\)
* A reference to an attribute of another parameter.
* Nested syntax is supported \(ie., `foo.bar.baz`\)

These constraints are applicable on types too - see type constraints for an example.

## Retry policies

When integrating with external REST APIs, handling intermittent failures such as HTTP 500 errors is crucial for maintaining application reliability. 

Taxi allows defining retry policies, which Orbital will honour when things go wrong

### Fixed Delay Retry Policy

A fixed delay retry policy retries operations with a constant wait time between attempts. This is useful for temporary issues on the external service's end.

For example, to retry the `getReviews` operation upon receiving an HTTP 500 error, with a 5-second wait for up to 10 attempts, define the service as follows:

```taxi
service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500], fixedRetryPolicy = @HttpFixedRetryPolicy(maxRetries = 10, retryDelay = 5))
    operation getReviews(id: FilmId): FilmReview[]
}
```

To include additional error codes like HTTP 502, simply adjust the `responseCode` attribute:

```taxi
service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500, 502], fixedRetryPolicy = @HttpFixedRetryPolicy(maxRetries = 10, retryDelay = 5))
    operation getReviews(id: FilmId): FilmReview[]
}
```

### Exponential Delay Retry Policy

An exponential delay retry policy increases the delay between retries, which can be further randomized using jitter to avoid synchronized retry storms:

```taxi
service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500, 502], fixedRetryPolicy = @HttpExponentialRetryPolicy(maxRetries = 10, retryDelay = 5, jitter = 0.5))
    operation getReviews(id: FilmId): FilmReview[]
}
```

With the above definition, Orbital will try invoking the Rest at most 10 times in case of Http 500 or Http 502 errors.

However, the delay between retries will increase exponentially.

With a `jitter` value between 0 and 1, you add some randomness to the delay time by introducing a random delay, or “jitter”, to the next retry delay time.
This ensures that the retries are not synchronous and reduces the likelihood of a retry storm.

Here’s how the table would look with exponential backoff with Jitter:

| Retry Attempt | Delay Time (seconds)  | 	Jitter Range (seconds) | Actual Delay Time (seconds) |
| ------------- | ---- -----------------|------------------------- |-----------------------------|
| 1             | 1.0                   | 0.5                      | 1-0 - 1.5                   |
| 2             | 2.0                   | 0.5                      | 1-5 - 2.5                   |
| 3             | 4.0                   | 0.5                      | 3.5 – 4.5                   |
| 4             | 8.0                   | 0.5                      | 7.5 – 8.5                   |
| 5             | 16.0                  | 0.5                      | 15.5 – 16.5                 |

## Modifying server URLs
Sometimes you need to modify the actual server name that's used for contacting an HTTP server.

Services define the url using the `@HttpOperation` annotation:

```taxi
service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews")
    operation getReviews(): FilmReview[]
}
```

However, it's possible that between environments you may need to direct traffic to a different
physical server.

Orbital supports overriding server domain names using a `services.conf` HOCON file, defined in your Taxi project.

### Declaring config files
Ensure that your Taxi project's `taxi.conf` file has the following config section declared:

```
additionalSources: {
    "@orbital/config" : "orbital/config/*.conf",
}
```

In your taxi project, add file at `orbital/config/services.conf`:

```hocon
services {
  reviews {
    url="http://localhost:8080"
  }
}
```


**Note**
services.conf files support [environment-specific variations](/docs/deploying/configuring-orbital#environment-specific-configuration).

Now, when Orbital sends HTTP requests to `http://reviews`, the URL will be rewritten to `http://localhost:8080`
