Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

OpenSearch clients implement various high-level REST DSLs to invoke OpenSearch APIs. Efforts such as opensearch-clients#19 aim at generating these from spec in order to always be up-to-date with the default distribution, including plugins. However this is a game that cannot be won. Clients will always lag behind, and users often find themselves in a situation that requires them to invoke an API that is not supported by the client. Thus, in opensearch-clients#62 I proposed we level up all OpenSearch language clients in their capability to make raw JSON REST requests. You help on these issues would be very much appreciated.

In this post I’ll keep current state with links to working samples, similar to Making AWS SigV4 Authenticated Requests to Amazon OpenSearch. For all these I am running a local copy of OpenSearch 2.9 in docker.

docker run \
  -p 9200:9200 \
  -p 9600:9600 \
  -e "discovery.type=single-node" \
  opensearchproject/opensearch:latest

Command Line

We’ll be looking for the equivalent of the four GET, POST, PUT and DELETE operations.

curl

curl -k -u admin:admin https://localhost:9200
{
  "name" : "5d98546c8098",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "Hu0dA0iYREiBVPqEuHqYaA",
  "version" : {
    "distribution" : "opensearch",
    "number" : "2.9.0",
    "build_type" : "tar",
    "build_hash" : "1164221ee2b8ba3560f0ff492309867beea28433",
    "build_date" : "2023-07-18T21:22:48.164885046Z",
    "build_snapshot" : false,
    "lucene_version" : "9.7.0",
    "minimum_wire_compatibility_version" : "7.10.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}
curl -k -u admin:admin \
  -X POST \
  -H "Content-type:application/json" \
  --data '{"director":"Bennett Miller","title":"Moneyball","year":2011}' \
  https://localhost:9200/movies/_doc/1 | jq
{
  "_index": "movies",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}
curl -k -u admin:admin \
  -X GET \
  https://localhost:9200/movies/_doc/1 | jq
{
  "_index": "movies",
  "_id": "1",
  "_version": 1,
  "_seq_no": 2,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "director": "Bennett Miller",
    "title": "Moneyball",
    "year": 2011
  }
}
curl -k -u admin:admin \
  -X PUT \
  -H "Content-type:application/json" \
  --data '{"director":"Bennett Miller","title":"Moneyball","year":2011}' \
  https://localhost:9200/movies/_doc/1 | jq
{
  "_index": "movies",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 1
}
curl -k -u admin:admin \
  -X DELETE \
  https://localhost:9200/movies/_doc/1 | jq
{
  "_index": "movies",
  "_id": "1",
  "_version": 4,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}

Java

opensearch-java

Feature request, opensearch-java#257.

Ruby

opensearch-ruby

Feature request, opensearch-ruby#209. Should also be possible via client.perform_request.

Node.js

opensearch-js

Feature request, opensearch-js#631. Should also be possible via transport.request.

Python

opensearch-py

The Python client exposes client.transport.perform_request.

info = client.transport.perform_request('GET', '/')
print(f"Welcome to {info['version']['distribution']} {info['version']['number']}!")
document = {
  'title': 'Moneyball',
  'director': 'Bennett Miller',
  'year': '2011'
}

client.transport.perform_request("PUT", "/movies/_doc/1?refresh=true", body = document)
query = {
  'size': 5,
  'query': {
    'multi_match': {
      'query': 'miller',
      'fields': ['title^2', 'director']
    }
  }
}

client.transport.perform_request("POST", "/movies/_search", body = query)
client.transport.perform_request("DELETE", "/movies")

See the updated documentation and working demo for more information. I also made a PR for a higher level DSL.

DotNet

opensearch-net

Feature request, opensearch-net#403.

Rust

opensearch-rs

The rust client directly supports JsonBody<_> on request, and .json() on response.

let info: Value = client
    .send::<(), ()>(Method::Get, "/", HeaderMap::new(), None, None, None)
    .await?
    .json()
    .await?;
    
println!(
    "{}: {}",
    info["version"]["distribution"].as_str().unwrap(),
    info["version"]["number"].as_str().unwrap()
);
let document: JsonBody<_> = json!({
    "title": "Moneyball",
    "director": "Bennett Miller",
    "year": "2011"
}).into();

client.send(
    Method::Put,
    "movies/_doc/1",
    HeaderMap::new(),
    Some(&[("refresh", "true")]),
    Some(document),
    None,
).await?;
let query: JsonBody<_> = json!({
  "size": 5,
  "query": {
      "multi_match": {
          "query": "miller",
          "fields": ["title^2", "director"]
      }
  }
}).into();

let search_response = client.send(
    Method::Post,
    &"/movies/_search",
    HeaderMap::new(),
    Option::<&()>::None,
    Some(query),
    None,
)
.await?;

let search_result = search_response.json::<Value>().await?;

println!("Hits: {:#?}", search_result["hits"]["hits"].as_array().unwrap());
client.send::<(), ()>(
  Method::Delete,
  "/movies",
  HeaderMap::new(),
  None,
  None,
  None,
)
.await?;

See the updated user guide, a working demo and a API vs. raw JSON diff for more information.

PHP

opensearch-php

Feature request, opensearch-php#166.

Go

opensearch-go

Feature request, opensearch-go#395.