# 以 RESTful 操作 DB

我們先前用的 `SDK` 為其他人封裝好方便我們用的，但我們必須要自己了解其原理，於是這篇文章將講解 SDK 內部的原理，利用單純的 `HTTPS` 請求來操作 `Cosmos DB` 。 ![/images/emoticon/emoticon61.gif](https://github.com/easonwang01/web_advance/tree/1925ddcb36447378ab5377e38c84f5ccccca8136/images/emoticon/emoticon61.gif)

## HTTPS Request 內容

發送 `HTTPS Request` 通常包含幾個部分：`Endpoint`、`Request Header`、`Request Body`。

#### 1. 請求位置（Endpoint）

首先我們要知道我們要發送請求的位置，類似於網頁的 URL。存取不同的資源必須填上不同的值，`Cosmos DB` 中最常見包含如下幾種 `URL`： ![https://ithelp.ithome.com.tw/upload/images/20181104/20112426EzKK7pXnwX.png](https://ithelp.ithome.com.tw/upload/images/20181104/20112426EzKK7pXnwX.png)

#### 2. 請求 `Header`

再來為了讓 `Cosmos DB` 辨識存取資源的人之身份與相關資訊，我們必須加上 `Header` 欄位，首先我們看到 `Request Header` 的文件：<https://docs.microsoft.com/en-us/rest/api/cosmos-db/common-cosmosdb-rest-request-headers>

文件內寫出了必要的請求 Header 包含：

```
{
  Authorization: "產生相關 authorization token",
  Content-Type:  "請求格式",
  x-ms-date:     "UTC 格式的當前時間",
  x-ms-version:  "API 版本號碼",
}
```

接著我們要來從第一個 Header 欄位開始填寫。

#### `Authorization` 欄位

首先我們要產生 Header 內的 `Authorization` 欄位，必須為如下格式：

```
type={tokenType}&ver={tokenVersion}&sig={hashSignature}
```

> tokenType 為 `master` 或是 `resource` tokenVersion 通常為 `1.0`

`hashSignature` 產生步驟： 1. 把 `masterKey` 轉為 `base64` 格式 2. 然後用 `HMAC-SHA256`做雜湊，其中 HMAC 的 key 為剛才第一步驟產生的 `base64` 值 3. `HAMC-SHA256` 的 body 內容為 `verb, resourceType, resourceId, date,` 串接的值，且每個欄位後要空行

看完後可能一頭霧水，不過沒關係，下面我們用程式來實做看看。

### Authorization Header 程式範例：

```
function genAuthHeader(
  verb,
  resourceType,
  resourceId,
  date,
  master_Key
) {
  const key = new Buffer(master_Key, "base64");

  const text =
    (verb || "").toLowerCase() +
    "\n" +
    (resourceType || "").toLowerCase() +
    "\n" +
    (resourceId || "") +
    "\n" +
    date.toLowerCase() +
    "\n" +
    "" +
    "\n";
  const body = new Buffer(text, "utf8");
  const signature = crypto
    .createHmac("sha256", key)
    .update(body)
    .digest("base64");

  const tokenType = "master";
  const TokenVersion = "1.0";

  return encodeURIComponent(
    "type=" + tokenType + "&ver=" + TokenVersion + "&sig=" + signature
  );
}
```

如此我們就產生了第一個 Authorization 欄位，不過還有剩下三個欄位，我們可以用以下方式產生他們：

```
{
  Content-Type:  'application/json',
  x-ms-date:     new Date(Date.now()).toUTCString(),
  x-ms-version:  '2018-06-18',  // 照著填此值即可
}
```

如此我們就完成 `Request Header` 了，因為這個範例是 `GET` 請求，不會有 `Request Body`，所以我們有了 `Endpoint` 和 `Request Header`，就可以來發送請求，我們馬上用以下範例試試看。

以下範例將會讀取一筆我們已經存放入資料庫的資料內容。

## 完整程式範例

```
const https = require("https");
const crypto = require("crypto");

const db_id = "TestDB";
const coll_id = "Fruits";
const resourceType = "docs";
const resourceLink = `dbs/${db_id}/colls/${coll_id}/docs/fd327d79-fb20-f5ab-fc81-6e28482670b3`;

const masterKey = "填上 Master Key";
const UTC_Date = new Date(Date.now()).toUTCString();
const method = "GET";

const options = {
  host: "yicheng.documents.azure.com",
  port: 443,
  path: `/` + resourceLink,
  method,
  headers: {
    authorization: genAuthHeader(
      method,
      resourceType,
      resourceLink,
      UTC_Date,
      masterKey
    ), 
    "x-ms-version": '2018-06-18',
    "x-ms-date": UTC_Date,
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  }
};

function sendRequest() {
  const req = https.request(options, res => {
    console.log(res.statusCode);
    res.on("data", function(data) {
      console.log(data.toString());
    });
  });

  req.on("error", e => {
    console.error(e);
  });

  req.end();
}

sendRequest();

function genAuthHeader(
  verb,
  resourceType,
  resourceId,
  date,
  master_Key
) {
  const key = new Buffer(master_Key, "base64");

  const text =
    (verb || "").toLowerCase() +
    "\n" +
    (resourceType || "").toLowerCase() +
    "\n" +
    (resourceId || "") +
    "\n" +
    date.toLowerCase() +
    "\n" +
    "" +
    "\n";
    const body = new Buffer(text, "utf8");
  const signature = crypto
    .createHmac("sha256", key)
    .update(body)
    .digest("base64");

  const tokenType = "master";
  const TokenVersion = "1.0";
  return encodeURIComponent(
    "type=" + tokenType + "&ver=" + TokenVersion + "&sig=" + signature
  );
}
```

回傳如下： ![https://ithelp.ithome.com.tw/upload/images/20181104/20112426tSmnelsZ6j.png](https://ithelp.ithome.com.tw/upload/images/20181104/20112426tSmnelsZ6j.png)

各位是否都成功接收到了回傳的資料了呢？接著我們來看看如果是使用 `SDK` 該如何做。

### 對應的 SDK 寫法：

```
const cosmos = require('@azure/cosmos');
const CosmosClient = cosmos.CosmosClient;

const endpoint = "https://yicheng.documents.azure.com:443/";
const masterKey = "填上金鑰";
const client = new CosmosClient({ endpoint, auth: { masterKey } });
const databaseDefinition = { id: 'TestDB' };
const collectionDefinition = { id: 'Fruits' };

async function createFruit() {
  const { body } = await client.database(databaseDefinition.id)
  .container(collectionDefinition.id)
  .item('fd327d79-fb20-f5ab-fc81-6e28482670b3').read();
};

createFruit().catch(err => {
  console.error(err);
});
```

後記：

> 使用 `Cosmos DB` 的 `RESTful API` 其中有很雷的地方是： `resourceLink` 前面不可以有 `/`，筆者先前將 `resourceLink` 寫為 `/dbs...`，發送後總是回傳 `401 Unauthorized`，這時非常納悶，後來一直與 SDK 版本的 payload 比對了兩個小時，後來才發現原來 `resourceLink` 是前面多了一個 `/` ，導致產生的雜湊一直不符合預期，必須要把 `/` 換成寫在 host 的前面才可以 `path: '/' + resourceLink` 。 ![/images/emoticon/emoticon20.gif](https://github.com/easonwang01/web_advance/tree/1925ddcb36447378ab5377e38c84f5ccccca8136/images/emoticon/emoticon20.gif)

參考資料：<https://docs.microsoft.com/en-us/rest/api/cosmos-db/restful-interactions-with-cosmosdb-resources>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://easonwang.gitbook.io/web_advance/azure/cosmos-db/yi-restful-cao-zuo-db.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
