Http API
========

[`Http API` online tutorial](https://arthas.aliyun.com/doc/arthas-tutorials.html?language=en&id=case-http-api)

### Overview

Http API provides a RESTful-like interactive interface, and both
requests and responses data in JSON format. Compared with
Telnet/WebConsole's output unstructured text data, Http API can provide
structured data and support more complex interactive functions, such as
a series of diagnostic operations in specific application scenarios.


#### Access address

The Http API address is: `http://ip:port/api`, the request parameters
must be submitted using `POST`. Such as POST
`http://127.0.0.1:8563/api`.

Note: The telnet port `3658` has compatibility issues with the Chrome
browser. It is recommended to use the http port `8563` to access the
http api.


#### Request data format

```json
{
  "action": "exec",
  "requestId": "req112",
  "sessionId": "94766d3c-8b39-42d3-8596-98aee3ccbefb",
  "consumerId": "955dbd1325334a84972b0f3ac19de4f7_2",
  "command": "version",
  "execTimeout": "10000"
}
```
Request data format description:

* `action` : The requested action/behavior, please refer to "Request
  Actions" for optional values.
*  `requestId` : Optional request ID, generated by the client.
*  `sessionId` : Arthas session ID, one-time command does not need to
   set the session ID.
*  `consumerId` : Arthas consumer ID, used for multi-person sharing
   sessions.
*  `command` : Arthas command line
*  `execTimeout` : Timeout for executing commands (ms), default value is 30000.

Note: Different actions use different parameters. Set the parameters
according to the specific action.

#### Request Actions

Currently supported request actions are as follows:

* `exec` : The command is executed synchronously, and the command
  results is returned after the command execution end or interrupted.
*  `async_exec` : The command is executed asynchronously, and the
   scheduling result of the command is returned immediately. The command
   execution result is obtained through `pull_results` action.
*  `interrupt_job` : To interrupt the foreground command of the session,
   similar to the function of Telnet `Ctrl + c`.
*  `pull_results` : Get the result of the command executed
   asynchronously, and execute it repeatedly in http long-polling mode.
*  `init_session` : Create new session.
*  `join_session` : Join the session, used to support multiple people
   sharing the same Arthas session.
*  `close_session` : Close the session.

#### Response status

The state attribute in the response indicates the request processing
state, and its value is as follows:

* `SCHEDULED`: When the command is executed asynchronously, it means that
  the job has been created, and may not be executed yet or is being
  executed;
* `SUCCEEDED`: The request is processed successfully (completed status);
* `FAILED`: Request processing failed (completed status), usually
  accompanied by a message explaining the reason;
* `REFUSED`: The request is rejected (completed status), usually
  accompanied by a message explaining the reason; 

### One-time command

Similar to executing batch commands, the one-time commands are executed
synchronously. No need to create a session, no need to set the
`sessionId` option.

```json
{
  "action": "exec",
  "command": "<Arthas command line>"
}
```

For example, get the Arthas version number:

```bash
curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"exec",
  "command":"version"
}
'
```
The response is as follows:

```json
{
   "state" : "SUCCEEDED",
   "sessionId" : "ee3bc004-4586-43de-bac0-b69d6db7a869",
   "body" : {
      "results" : [
         {
            "type" : "version",
            "version" : "3.3.7",
            "jobId" : 5
         },
         {
            "jobId" : 5,
            "statusCode" : 0,
            "type" : "status"
         }
      ],
      "timeExpired" : false,
      "command" : "version",
      "jobStatus" : "TERMINATED",
      "jobId" : 5
   }
}
```
Response data format description:

* `state`: Request processing status, refer to the description of
  "Response Status".
*  `sessionId `: Arthas session ID, one-time command to automatically
   create and destroy temporary sessions.
*  `body.jobId`: The job ID of the command, all output results of the
   same job are the same jobId.
*  `body.jobStatus`: The job status of the command.
*  `body.timeExpired`: Whether the job execution timed out.
* `body/results`: Command execution results.

**Command result format description**

```json
 [{
    "type" : "version",
    "version" : "3.3.7",
    "jobId" : 5
 },
 {
    "jobId" : 5,
    "statusCode" : 0,
    "type" : "status"
 }]
```

* `type` : The command result type, except for the special ones such as
  `status`, the others remain the same as the Arthas command name.
  Please refer to the section
  "[Special command results](#special_command_results)".
*  `jobId` : The job ID of the command.
*  Other fields are the data of each different command.

Note: You can also use a one-time command to execute continuous output
commands such as watch/trace, but you can't interrupt the command
execution, and there may be hang up for a long time. Please refer to the
example in the
"[Make watch command output a map object](#change_watch_value_to_map)"
section.

Please try to deal with it in the following way:

* Set a reasonable `execTimeout` to forcibly interrupt the command
  execution after the timeout period is reached to avoid a long hang.
* Use the `-n` parameter to specify a smaller number of executions.
* Ensure the methods of the command matched can be successfully hit and
  the `condition-express` is written correctly. If the `watch/trace` does
  not hit, even if `-n 1` is specified, it will hang and wait until the
  execution timeout.



### Session interaction

Users create and manage Arthas sessions, which are suitable for complex
interactive processes. The access process is as follows:

* Create a session
* Join the session (optional)
* Pull command results
* Execute a series of commands
* Interrupt command execution
* Close the session

#### Create session

```bash
curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"init_session"
}
'
```
Response result:

```json
{
   "sessionId" : "b09f1353-202c-407b-af24-701b744f971e",
   "consumerId" : "5ae4e5fbab8b4e529ac404f260d4e2d1_1",
   "state" : "SUCCEEDED"
}
```
The new session ID is: `b09f1353-202c-407b-af24-701b744f971e`, and
consumer ID is: `5ae4e5fbab8b4e529ac404f260d4e2d1_1`.


#### Join session

Specify the session ID to join, and the server will assign a new
consumer ID. Multiple consumers can receive the same command results of
target session. This interface is used to support multiple people
sharing the same session or refreshing the page to retrieve the session
history.

```bash
curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"join_session",
  "sessionId" : "b09f1353-202c-407b-af24-701b744f971e"
}
'
```
Response result:

```json
{
   "consumerId" : "8f7f6ad7bc2d4cb5aa57a530927a95cc_2",
   "sessionId" : "b09f1353-202c-407b-af24-701b744f971e",
   "state" : "SUCCEEDED"
}
```
The new consumer ID is `8f7f6ad7bc2d4cb5aa57a530927a95cc_2 ` .

#### Pull command results

The action of pulling the command result message is `pull_results`.
Please use the Http long-polling method to periodically pull the result
messages. The consumer's timeout period is 5 minutes. After the timeout,
you need to call `join_session` to allocate a new consumer.

Each consumer is allocated a cache queue separately, and the pull order
does not affect the content received by the consumer.


The request parameters require session ID and consumer ID:

```bash
curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"pull_results",
  "sessionId" : "b09f1353-202c-407b-af24-701b744f971e",
  "consumerId" : "8f7f6ad7bc2d4cb5aa57a530927a95cc_2"
}
'
```

Use Bash scripts to regularly pull results messages:

```bash
while true; do curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"pull_results",
  "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a",
  "consumerId" : "8ecb9cb7c7804d5d92e258b23d5245cc_1"
}
' | json_pp; sleep 2; done
```

Note: The `json_pp` tool formats the output content as pretty json.

The response content is as follows:

```json
{
   "body" : {
      "results" : [
         {
            "inputStatus" : "DISABLED",
            "jobId" : 0,
            "type" : "input_status"
         },
         {
            "type" : "message",
            "jobId" : 0,
            "message" : "Welcome to arthas!"
         },
         {
            "tutorials" : "https://arthas.aliyun.com/doc/arthas-tutorials.html",
            "time" : "2020-08-06 15:56:43",
            "type" : "welcome",
            "jobId" : 0,
            "pid" : "7909",
            "wiki" : "https://arthas.aliyun.com/doc",
            "version" : "3.3.7"
         },
         {
            "inputStatus" : "ALLOW_INPUT",
            "type" : "input_status",
            "jobId" : 0
         }
      ]
   },
   "sessionId" : "b09f1353-202c-407b-af24-701b744f971e",
   "consumerId" : "8f7f6ad7bc2d4cb5aa57a530927a95cc_2",
   "state" : "SUCCEEDED"
}

```


#### Execute commands asynchronously

```bash
curl -Ss -XPOST http://localhost:8563/api -d '''
{
  "action":"async_exec",
  "command":"watch demo.MathGame primeFactors \"{params, returnObj, throwExp}\" ",
  "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a"
}
'''
```

Response of `async_exec`:

```json
{
   "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a",
   "state" : "SCHEDULED",
   "body" : {
      "jobStatus" : "READY",
      "jobId" : 3,
      "command" : "watch demo.MathGame primeFactors \"{params, returnObj, throwExp}\" "
   }
}
```

* `state` : The status of `SCHEDULED` means that the command has been
  parsed and generated the job, but the execution has not started.
* `body.jobId` : The job id of command execution, filter the command
  results output in `pull_results` according to this job ID.
* `body.jobStatus` : The job status `READY` means that execution has not started.

The shell output of the script that continuously pulls the result message:

```json
{
   "body" : {
      "results" : [
         {
            "type" : "command",
            "jobId" : 3,
            "state" : "SCHEDULED",
            "command" : "watch demo.MathGame primeFactors \"{params, returnObj, throwExp}\" "
         },
         {
            "inputStatus" : "ALLOW_INTERRUPT",
            "jobId" : 0,
            "type" : "input_status"
         },
         {
            "success" : true,
            "jobId" : 3,
            "effect" : {
               "listenerId" : 3,
               "cost" : 24,
               "classCount" : 1,
               "methodCount" : 1
            },
            "type" : "enhancer"
         },
         {
            "sizeLimit" : 10485760,
            "expand" : 1,
            "jobId" : 3,
            "type" : "watch",
            "cost" : 0.071499,
            "ts" : 1596703453237,
            "value" : [
               [
                  -170365
               ],
               null,
               {
                  "stackTrace" : [
                     {
                        "className" : "demo.MathGame",
                        "classLoaderName" : "app",
                        "methodName" : "primeFactors",
                        "nativeMethod" : false,
                        "lineNumber" : 46,
                        "fileName" : "MathGame.java"
                     },
                     ...
                  ],
                  "localizedMessage" : "number is: -170365, need >= 2",
                  "@type" : "java.lang.IllegalArgumentException",
                  "message" : "number is: -170365, need >= 2"
               }
            ]
         },
         {
            "type" : "watch",
            "cost" : 0.033375,
            "jobId" : 3,
            "ts" : 1596703454241,
            "value" : [
               [
                  1
               ],
               [
                  2,
                  2,
                  2,
                  2,
                  13,
                  491
               ],
               null
            ],
            "sizeLimit" : 10485760,
            "expand" : 1
         }
      ]
   },
   "consumerId" : "8ecb9cb7c7804d5d92e258b23d5245cc_1",
   "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a",
   "state" : "SUCCEEDED"
}
```


The `value` of the watch command result is the value of watch-experss,
and the above command is `{params, returnObj, throwExp}`, so the value
of the watch result is an array of length 3, and each element
corresponds to the expression in the corresponding order.

Please refer to the section "[Make watch command output a map object](#change_watch_value_to_map)".

#### Interrupt command execution

Interrupt the running foreground job of the session:

```bash
curl -Ss -XPOST http://localhost:8563/api -d '''
{
  "action":"interrupt_job",
  "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a"
}
'''
```

```json
{
   "state" : "SUCCEEDED",
   "body" : {
      "jobStatus" : "TERMINATED",
      "jobId" : 3
   }
}
```

#### Close session

Specify the session ID to close the session.

```bash
curl -Ss -XPOST http://localhost:8563/api -d '''
{
  "action":"close_session",
  "sessionId" : "2b085b5d-883b-4914-ab35-b2c5c1d5aa2a"
}
'''
```

```json
{
   "state" : "SUCCEEDED"
}
```

### Authentication

* Reference: [auth](auth.md)

### Web UI

![](../_static/arthas-web-ui.png "arthas web ui")

A Web UI based on Http API, visit url :
[http://127.0.0.1:8563/ui](http://127.0.0.1:8563/ui) .

Completed functions:

* Create a session
* Copy and open the url to join the session, share the session with
  multiple people
* Continuously pull session command result messages
* Refresh the web page or join the session to pull command messages
  history
* Control input or interrupt command status


Pending function:

* Improve the readability of command result messages
* Support automatic completion of input commands and command templates
* Provide command help
* Support personal profile settings

<a id="special_command_results"></a>
### Special command results

#### status

```json
{
    "jobId" : 5,
    "statusCode" : 0,
    "type" : "status"
}
```

`type` is `status` to indicate the command execution status:


After each command is executed, there is a unique status result. If the
`statusCode` is 0, it means the execution is successful, and the
`statusCode` is a non-zero value that means the execution failed,
similar to the process exit code.

When the command execution fails, an error message is generally provided, such as:

```json
{
  "jobId":3,
  "message":"The argument 'class-pattern' is required",
  "statusCode":-10,
  "type":"status"
}
```

#### input_status

```json
 {
    "inputStatus" : "ALLOW_INPUT",
    "type" : "input_status",
    "jobId" : 0
 }
```

`type` is `input_status` to indicate input status:


It is used to control user input during UI interaction, and a change
message will be sent before and after each command is executed.

Possible values ​​of `inputStatus`:

* `ALLOW_INPUT` : Allow users to enter commands, which means that the
  session has no foreground command being executed and can accept new
  command.
* `ALLOW_INTERRUPT` : Allow the user to interrupt the command execution,
  indicating that a command is currently being executed, and the user
  can send `interrupt_job` to interrupt the execution.
* `DISABLED` : In the disabled state, commands cannot be entered or
  interrupted.


#### command

```json
 {
    "type" : "command",
    "jobId" : 3,
    "state" : "SCHEDULED",
    "command" : "watch demo.MathGame primeFactors \"{params, returnObj, throwExp}\" "
 }
```
`type` is `command` to indicate the input command data:

It is used for the interactive UI to echo the commands entered by the
user. The pulled session command message history will contain messages
of type `command`, which can be processed in order.

#### enhancer

```json
 {
    "success" : true,
    "jobId" : 3,
    "effect" : {
       "listenerId" : 3,
       "cost" : 24,
       "classCount" : 1,
       "methodCount" : 1
    },
    "type" : "enhancer"
 }
```

`type` is `enhancer` to indicate the result of class enhancement:

Commands such as `trace/watch/jad/tt` need to enhance the class and will
receive this `enhancer` result. It may happen that the result of
`enhancer` is successful, but there is no hit method. The client can
prompt the user according to the result of `enhancer`.

### Cases

#### Get classpath of Java application

Get system properties of the Java application through Http api and
extract the value of `java.class.path`.

```bash
json_data=$(curl -Ss -XPOST http://localhost:8563/api -d '
{
  "action":"exec",
  "command":"sysprop"
}')
```

* Extract value with `sed`:

```bash
class_path=$(echo $json_data | tr -d '\n' | sed 's/.*"java.class.path":"\([^"]*\).*/\1/')
echo "classpath: $class_path"
```

* Extract value with `json_pp/awk`:

```bash
class_path=$(echo $json_data | tr -d '\n' | json_pp | grep java.class.path | awk -F'"' '{ print $4 }')
echo "classpath: $class_path"
```

Output:

```
classpath: demo-arthas-spring-boot.jar
```

NOTE:

* `echo $json_data | tr -d '\n'` : Delete line breaks (the value of
  `line.separator`) to avoid affecting the processing of `sed`/`json_pp`
  commands.
* `awk -F'"' '{ print $4 }'` : Use double quote as delimiter


<a id="change_watch_value_to_map"></a>
#### Make watch command output a map object

The result value of `watch` is generated by calculating the
`watch-express` ognl expression. You can change the ognl expression to
generate the desired value, please refer to
[OGNL document](https://commons.apache.org/proper/commons-ognl/language-guide.html).

> Maps can also be created using a special syntax.
>
>#{ "foo" : "foo value", "bar" : "bar value" }
>
>This creates a Map initialized with mappings for "foo" and "bar".

The following command generates values ​​in map format:

```bash
watch *MathGame prime* '#{ "params" : params, "returnObj" : returnObj, "throwExp": throwExp}' -x 2 -n 5
```

Execute the above command in Telnet shell/WebConsole, the output result:

```bash
ts=2020-08-06 16:57:20; [cost=0.241735ms] result=@LinkedHashMap[
    @String[params]:@Object[][
        @Integer[1],
    ],
    @String[returnObj]:@ArrayList[
        @Integer[2],
        @Integer[241],
        @Integer[379],
    ],
    @String[throwExp]:null,
]
```

Execute the above command with Http api, pay attention to escaping the JSON double quotes:

```bash
curl -Ss -XPOST http://localhost:8563/api -d @- << EOF
{
  "action":"exec",
  "execTimeout": 30000,
  "command":"watch *MathGame prime* '#{ \"params\" : params, \"returnObj\" : returnObj, \"throwExp\": throwExp}' -n 3 "
}
EOF
```

Http api execution result:

```json
{
    "body": {
         ...
        "results": [
            ...
            {
                ...
                "type": "watch",
                "value": {
                    "params": [
                        1
                    ],
                    "returnObj": [
                        2,
                        5,
                        17,
                        23,
                        23
                    ]
                }
            },
            {
                ...
                "type": "watch",
                "value": {
                    "params": [
                        -98278
                    ],
                    "throwExp": {
                        "@type": "java.lang.IllegalArgumentException",
                        "localizedMessage": "number is: -98278, need >= 2",
                        "message": "number is: -98278, need >= 2",
                        "stackTrace": [
                            ...
                        ]
                    }
                }
            },
            ...
}
```

You can see that the value of the watch result becomes a map object, and
the program can read value through a key .
