mirror of
https://github.com/alibaba/arthas.git
synced 2026-11-13 06:42:48 +00:00
757 lines
20 KiB
Plaintext
757 lines
20 KiB
Plaintext
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"
|
||
}
|
||
```
|
||
|
||
### 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 .
|