본문으로 건너뛰기

REST API (v1)

PlantPulse Edge 의 v1 REST API 매뉴얼. 화면 / 외부 시스템이 OPC, 태그, 모니터링, 외부 전송 헬스체크를 다루는 표준 인터페이스 입니다.

항목
Base URLhttp(s)://<edge-host>/api/v1
Content-Typeapplication/json; charset=utf-8
인증API Key 강제 (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer, 또는 로그인 세션으로 통과. ?api_key= 는 401 로 거부
Audit 로그ApiAccessLogFilter — 모든 /api/* 호출을 한 줄로 기록 (메서드/경로/상태/지속/IP/사용자/auth 모드)
메인 컨트롤러plantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI)
오류 envelope{data, meta, errors} — filter/advice 경로도 동일
HTTPSedge.rest.api.https_only=true 기본. plain HTTP client 호출은 거부 또는 Tomcat HTTPS redirect 정책을 따른다

인증 (ApiAuthFilter)

app.properties 에서 edge.rest.api.auth=true 로 활성화. 키는 edge.rest.api.key 이며 운영에서는 EDGE_REST_API_KEY_FILE 또는 EDGE_REST_API_KEY 로 주입합니다.

다음 3가지 중 하나라도 만족하면 통과:

A. 로그인 세션 통과 (브라우저)

이미 /login/form 으로 로그인한 사용자 — 세션에 _USER_LOGIN attribute (JSONObject) 가 있으면 ApiAuthFilter 가 그대로 chain.doFilter() 합니다. 화면(JSP) 에서 ajax 로 호출하는 모든 /api/* 가 추가 키 없이 동작합니다.

B. X-API-Key 헤더 (권장)

EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "X-API-Key: %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value

C. Authorization: Bearer <키> 헤더

EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value

D. ?api_key=<키> 쿼리 파라미터 — 거부됨

URL 에 키가 평문으로 들어가 액세스 로그/프록시 캐시/브라우저 history 에 남을 수 있으므로 v1 에서는 401 로 거부합니다. 헤더 방식(B/C)을 사용하세요.

실패 응답

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 401,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing X-API-Key."
}
]
}

키 비교는 constantTimeEquals() (timing-attack 방어).

edge.rest.api.auth=false (개발 / 임시)

인증을 끄면 ApiAuthFilter 가 즉시 chain.doFilter() — 외부에서 키 없이 호출 가능합니다. 운영망에서는 항상 true 권장.


Audit 로그 (ApiAccessLogFilter)

모든 /api/* 호출을 한 줄 로그로 기록 — 인증 실패(401) 도 포함되어 audit 트레일을 남깁니다 (필터 체인 순서: ApiAccessLogFilter → ApiAuthFilter).

형식

[API_ACCESS] method=GET path=/api/v1/tag/TAG_MS_1000/value query= status=200 duration=12ms
ip=100.127.147.98 user=admin auth=session ua=Mozilla/5.0...
필드의미
methodHTTP 메서드
pathrequest URI (쿼리 미포함)
query쿼리 문자열 — 거부되는 ?api_key=... 부분도 *** 로 마스킹
statusHTTP 응답 코드
duration처리 시간 (ms)
ip기본은 RemoteAddr. edge.rest.api.trust_x_forwarded_for=true 인 reverse proxy 배치에서만 X-Forwarded-For 첫 IP
usersession 사용자면 user_id / apikey 면 XXXX*** (len=16) (앞 4자 + 길이) / 익명이면 -
authsession (브라우저 로그인) / apikey (X-API-Key/Bearer) / deprecated_query (거부된 ?api_key= 시도) / none (키 누락 또는 익명)
uaUser-Agent (80자 초과 시 잘라짐)
error예외 발생 시 error=ClassName 추가

로그 레벨 분기

상태레벨의도
5xx 또는 throwableERROR서버 오류 — 즉시 알람
401, 403WARN인증/권한 거절 — audit 우선
그 외 (2xx, 3xx, 4xx 의 4xx 일반)INFO일상 호출

활용

  • 누가 키를 잘못 보내고 있나grep '\[API_ACCESS\].*status=401' 로 401 트레일 확인
  • 5xx 회귀 추적grep '\[API_ACCESS\].*status=5' + error= 필드
  • 호출 빈도 / 사용자별 집계 — auth=apikey/session 로 grouping, ip 별 분석
  • 응답 지연 슬로우 쿼리duration= 큰 라인 추출

키 평문은 어디에서도 로그에 남지 않습니다 — query 마스킹 + user 필드 prefix 4자 만으로 식별. 침해 사고 분석 에는 충분한 식별성 + 평문 노출 방지의 균형.

필터 체인 순서가 중요

web.xml 매핑 순서: ApiAccessLogFilter 가 먼저, 그 다음 ApiAuthFilter. AccessLog 가 finally 블록에서 status 를 기록하기 때문에 인증 실패한 401 호출도 그대로 audit 됩니다. 순서가 바뀌면 401 호출이 access log 에서 누락됩니다.


응답 envelope

v1 REST API 응답은 공통 envelope 으로 감쌉니다 (RestApiSupport, ApiEnvelopeWriter).

정상 응답:

{
"data": { },
"meta": {
"timestamp": 1778069486934,
"status": "OK",
"request_id": "f818abd0-eb0f-4497-9b07-7ccc94b2e2d7"
}
}

에러 응답:

{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 400,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "VALIDATION_FAILED",
"message": "Validation failed"
}
]
}

옛 RPC-style envelope (result, _session_id, _user_id) 는 REST v1 에서 사용하지 않습니다.


1. 엔드포인트 일람

1.1 OPC

슬러그MethodURL설명
flow_edge_opc_listGET/api/v1/opcOPC 목록 (tag_count / connection_status / scan_status 포함)
flow_edge_opc_createPOST/api/v1/opcOPC + 태그 묶음 등록
flow_edge_opc_updatePUT/api/v1/opc/{opcId}OPC + 태그 묶음 갱신 (path opcId 가 body opc_id 를 덮어씀)
flow_edge_opc_deleteDELETE/api/v1/opc/{opcId}OPC 및 하위 태그 일괄 삭제
flow_edge_opc_startPOST/api/v1/opc/{opcId}/start수집 시작
flow_edge_opc_stopPOST/api/v1/opc/{opcId}/stop수집 중지

1.2 Tag

슬러그MethodURL설명
flow_edge_tag_listGET/api/v1/opc/{opcId}/tag특정 OPC 의 태그 목록 (마지막 값/상태 포함)
flow_edge_tag_createPOST/api/v1/opc/{opcId}/tag단건 태그 등록
flow_edge_tag_updatePUT/api/v1/tag/{tagId}단건 태그 부분 갱신 (기존 row 머지 후 upsert)
flow_edge_tag_deleteDELETE/api/v1/tag/{tagId}단건 태그 삭제
flow_edge_tag_readGET/api/v1/tag/{tagId}/value[?fresh=true]캐시 값 (default), fresh=true 면 PLC 직접 read
flow_edge_tag_writePOST/api/v1/tag/{tagId}/value값 쓰기 — 모든 프로토콜 지원 (HTTP / OPC-UA / Modbus / MELSEC / S7 / LS / EIP)

1.3 Monitoring / Edge / Transfer

슬러그MethodURL설명
flow_edge_monitoringGET/api/v1/monitoringCPU/메모리/네트워크/디스크/스레드 등 MonitorBean 의 모든 필드
flow_edge_infoGET/api/v1/edge엣지 식별/버전/사이트/OS/가동시간 메타
flow_edge_transferGET/api/v1/transferoutbound transfer (API/MQTT/Sparkplug) 별 헬스 상태

1.3a System (인증 불필요 — LB / k8s probe / OTA 용)

/api/v1/system/* 중 아래 3개는 ApiAuthFilter 화이트리스트 통과 (어떤 OPC/Tag 정보도 노출 안 함).

MethodURL설명HTTP
GET/api/v1/system/health8개 컴포넌트 (cassandra / redis / mqtt / node_red / opc_ua / edge_core / tse / grafana) TCP 200ms probe + 종합 status200 (all UP) / 503 (DEGRADED)
GET/api/v1/system/readymonitor + V5 api_client 준비 상태200 / 503
GET/api/v1/system/versionMetadata.VERSION + BUILD_DATE + /etc/kopens/version.env image_tag + /.dockerenv container_mode200

/health 예시:

{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP",
"node_red": "UP", "opc_ua": "UP"
}
}
}

/version 예시:

{
"data": {
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "20260523",
"image_tag": "2026-20260523",
"container_mode": true
},
"meta": {
"timestamp": 1778925572946,
"request_id": "..."
}
}

OTA upgrade.sh/api/health (300초) probe 로 auto-rollback 판단. Docker HEALTHCHECK 도 /health 사용.

1.4 OPC-UA 뷰어 (OPCUAViewerAPI)

내장 OPC-UA 서버의 endpoint / 노드 트리 / 시계열 — /ui/opcua 화면이 사용.

MethodURL설명
GET/ui/opcua/info[?reveal=true]내장 OPC-UA 서버의 endpoint URL (TCP/TLS) / Application 이름 / 인증 정보 (reveal=true + 인증 세션 시 password 평문)
GET/ui/opcua/treeSite → OPC → Tag 트리 (NodeId 포함). EDGE_* 시스템 OPC 는 항상 connection_status=CONNECTED
GET/ui/opcua/history?tagId=...&minutes=N&limit=M카산드라 tm_tag_point 의 시계열 조회. 입력 검증 후 ASC 정렬된 points 배열 반환

1.5 업그레이드 / 시스템 (ConfigAPI)

/config/* 는 v1 REST API 와 별도 (관리자 화면용) 이지만 본 매뉴얼에서 같이 정리.

MethodURL설명
GET/config/upgrade/checkserver-to-server 로 product.kopens.io 의 VERSION.JSON 가져와 {result, latest_version, latest_build_date} 반환. 실패 시 result=ERROR
POST/config/upgradebin/upgrade.sh 실행 (장시간 작업)
POST/config/restartbin/restart.sh 실행 (Tomcat 재시작)
POST/config/rebootbin/reboot.sh 실행 (OS 재부팅)
POST/config/temp-cleanbin/clean.sh 실행 (로그/임시파일 정리)
POST/config/backupbin/backup.sh 실행
POST/config/firmwarebin/firmware.sh 실행 (dnf update -y)
GET/config/loadapp.properties 텍스트 반환
POST/config/saveapp.properties 저장

2. 요청 / 응답 예시

2.1 GET /api/v1/opc — OPC 목록

curl -s http://<edge-host>/api/v1/opc | jq
{
"result": "OK",
"data": {
"data": [
{
"opc_id": "OPC_UA_Kepware",
"opc_type": "OPCUA",
"opc_name": "OPC_UA_Kepware",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"auto_collect": "true",
"timecycle": "1000",
"options": {"username": "kopens", "password": "***", "discovery": "false"},
"tag_count": 11,
"point_count": 5819,
"connection_status": "CONNECTED",
"scan_status": "START"
}
]
}
}

data.data[] 형태 (이중 wrap) — 위 envelope 섹션 참고.

2.2 POST /api/v1/opc — OPC + 태그 묶음 등록

curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_NEW",
"opc_type": "OPCUA",
"opc_name": "신규 연결",
"opc_agent_ip": "10.0.0.10",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"tag_list": [
{"tag_id": "TAG_001", "tag_name": "Sine",
"plc_address": "ns=2;s=Sine1", "data_type": "Float"}
]
}'

2.3 POST /api/v1/opc/{opcId}/tag — 태그 단건 등록

{
"tag_id": "TAG_NEW",
"tag_name": "신규 태그",
"plc_address": "ns=2;s=NewTag",
"data_type": "Float",
"description": "..."
}

응답:

{ "result": "OK", "data": { "result": "SUCCESS", "tag_id": "TAG_NEW" } }

site_id 는 OPC 에서 자동으로 채워 넣습니다.

2.4 PUT /api/v1/tag/{tagId} — 태그 부분 갱신

요청 body 는 변경할 필드만 보냅니다. 서버에서 기존 row 와 머지 후 upsert.

{ "description": "변경된 설명" }

2.5 GET /api/v1/tag/{tagId}/value — 마지막 값 조회

기본 (캐시):

curl -s http://<edge-host>/api/v1/tag/TAG_UA_0004/value | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_UA_0004",
"value": "28.9688",
"value_time": "2026-05-06 20:59:39.000",
"value_read_status": "SUCCESS",
"value_read_error_message": ""
}
}

fresh=true (PLC 직접 read):

curl -s 'http://<edge-host>/api/v1/tag/TAG_UA_0004/value?fresh=true' | jq

PLC read 실패 시 외부 result=ERROR + message:

{ "result": "ERROR", "message": "fresh read failed: ..." }

⚠ 존재하지 않는 태그를 조회해도 envelope result=OK 로 응답하며 value"-" 로 채워진 placeholder 페이로드를 돌려줍니다 (현재 동작).

2.6 POST /api/v1/tag/{tagId}/value — 값 쓰기

curl -X POST http://<edge-host>/api/v1/tag/TAG_HTTP_001/value \
-H "Content-Type: application/json" \
-d '{ "value": "42" }'

성공:

{
"result": "OK",
"data": {
"result": "SUCCESS",
"opc_id": "OPC_LS_TEST",
"plc_address": "D1000",
"value": "42",
"actual_read": "42"
}
}

실패 (드라이버가 false 반환 / OPC DISCONNECTED / 스케줄러 미가동) — 외부 envelope result=ERROR:

{ "result": "ERROR", "message": "쓰기 실패 (opc_type=EIP) — EIP 펌웨어/패치에 따라 ..." }

지원 프로토콜: HTTP / OPC-UA / Modbus / MELSEC / S7 / LS XGT / EIP — 모두 가능. EIP 는 PLC 펌웨어/패치별 제약 가능.

2.7 POST /api/v1/opc/{opcId}/start — 수집 시작

curl -X POST http://<edge-host>/api/v1/opc/OPC_NEW/start

내장 OPCUA/MODBUS 시뮬레이터 자동 기동은 지원하지 않습니다. 테스트/데모 데이터는 외부 plantpulse-simulator 또는 테스트 태그를 사용하세요.

2.8 GET /api/v1/edge — 엣지 식별 / 가동 정보

EdgeAPI.edgeInfo() 가 직접 채우는 필드:

{
"result": "OK",
"data": {
"id": "EDGE_00303",
"site_id": "SITE_00001",
"site_name": "S1_LOTTE_CS_DJ_SITE",
"hostname": "EDGE-303",
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "2026-05-08",
"os_name": "Linux",
"os_version": "6.14.5-100.fc40.x86_64",
"os_arch": "amd64",
"started": true,
"started_date": 1778066700000,
"uptime_ms": 124500,
"always_on": false,
"ttl": 30,
"sended_count": 8063
}
}

2.9 GET /api/v1/monitoring — 시스템 메트릭

MonitorBean 의 모든 필드를 그대로 직렬화. 1초 갱신.

curl -s http://<edge-host>/api/v1/monitoring | jq '.data | keys'

대표 필드: cpu_used_percent, memory_used_percent, disk_used_percent, temperature, ping, api, opc_count, tag_count, mps, mps_history, queue_size, plc_value_read_success_count, plc_value_read_error_count, plc_value_write_success_count, plc_value_write_error_count, plc_con_connected_count, plc_con_disconnected_count, plc_scan_start_count, plc_scan_not_collect_count, plc_scan_stop_count, sended_point_count, sended_point_bytes, system_total_db_size, system_error_count, docker_on, docker_container_up_count, docker_container_total_count.

2.10 GET /api/v1/transfer — 외부 전송 헬스

curl -s http://<edge-host>/api/v1/transfer | jq
{
"result": "OK",
"data": {
"transfers": [
{ "type": "API", "enabled": true, "connected": true, "sent_count": 8063 },
{ "type": "MQTT", "enabled": true, "connected": true, "sent_count": 12345 },
{ "type": "Sparkplug", "enabled": true, "connected": true,
"group_id": "Plant1", "edge_node_id": "EDGE_00303",
"bdSeq": 7, "seq": 211 }
]
}
}

각 transfer 의 getStatus() 결과를 그대로 배열로 반환. 호출 중 예외 발생 시 {type, status_error} 로 폴백.

2.11 GET /ui/opcua/info — 내장 OPC-UA 서버 정보

curl -s http://<edge-host>/ui/opcua/info | jq
{
"result": "OK",
"data": {
"application_name": "PlantPulse Edge OPC-UA Server",
"product_uri": "urn:plantpulse:opcua:server",
"domain": "127.0.0.1",
"tcp_endpoint": "opc.tcp://127.0.0.1:12000",
"tls_endpoint": "opc.tcp://127.0.0.1:12443",
"namespace_index": 2,
"auth_username": "edge",
"auth_password_set": true,
"auth_anonymous": false
}
}

?reveal=true + 인증된 세션이면 auth_password 평문 추가.

2.12 GET /ui/opcua/tree — 노드 트리

Site → OPC → Tag 3단 트리. 각 태그에 NodeId, 최신 값, 데이터 타입, description 포함. EDGE 시스템 OPC 는 connection_status=CONNECTED 강제.

NodeId 컨벤션: ns=2;s=<SITE>.<OPC>.<TAG>.

2.13 GET /ui/opcua/history — 시계열

파라미터기본제한
tagId필수최대 200 자
minutes101 – 1440 (24h)
limit6001 – 5000
curl -s 'http://<edge-host>/ui/opcua/history?tagId=TAG_S7_1000&minutes=10&limit=100' | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_S7_1000",
"minutes": 10,
"count": 100,
"points": [
{ "ts": 1778176880010, "value": "211", "type": "integer", "quality": 192 }
]
}
}

tagId 누락/공백/200자 초과 시 외부 result=ERROR (message). 카산드라 timestamp 가 wrap 객체로 와도 extractTimestampMs 로 epoch ms 변환.


3. 에러 응답 패턴

케이스응답 형태
외부 envelope ERROR (write 실패 / fresh read 실패 / opcua/history 입력 검증 실패 / upgrade/check 다운로드 실패){result:"ERROR", message:"..."} (HTTP 200)
내부 비즈니스 ERROR (CRUD 시 tag/opc not found / DB 무결성 / 스케줄러 미가동){result:"OK", data:{result:"ERROR", msg:"..."}} (HTTP 200)
Spring 미스매핑 (필수 파라미터 누락, 잘못된 메서드 등)HTTP 500 + 스택트레이스 — 권장: 컨트롤러 단에서 @RequestParam(required=false) 처리 후 envelope 으로 변환
인증 — edge.rest.api.auth 가 설치 기본값 true 이며 ApiAuthFilterX-API-Key / Bearer 를 요구한다(위 인증 절 참조). 헬스 엔드포인트만 예외

4. 동작 메모

  • 태그 단건 조회: APP_TAG.xmlR03 (WHERE TAG_ID = :tag_id ALLOW FILTERING).
  • OPC + 태그 묶음 등록 / 단건 태그 등록ConnectService.restartComponents() 호출 → OPCAndTagsCache reload 및 OPC-UA 서버 재시작.
  • 값 쓰기 (tagWriteByTagId): HTTP 는 HTTPDriver.bind(), 그 외는 driver.write(ProtocolAddress). 실패 시 (ok=false) 친절 힌트 (EIP 펌웨어 호환성 등) 포함. write 후 즉시 read 한 결과를 actual_read 로 함께 반환 (HTTP 제외).
  • 트랜스퍼 헬스 (/api/v1/transfer): TransferRegistry 에 등록된 모든 outbound 컴포넌트의 getStatus() 를 모음. Sparkplug B 카드 (/ui/main) 가 5초 주기로 호출.
  • OPC-UA 뷰어 (/ui/opcua/*) 는 내부망 폴링 (2초) — OPCAndTagsCache.getInstance() + LastValueMap.getInstance() 만 사용해 부하 적음.
  • 레거시 deprecated: /api/http (APIController) — /api/v1/tag/{tagId}/value 사용 권장.

5. 코드 위치

  • 컨트롤러: src/plantpulse/app/edge/api/v1/OpcAPI.java · api/v1/TagAPI.java
  • OPC-UA 뷰어: src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java
  • 업그레이드 프록시: src/plantpulse/app/edge/module/system/config/ConfigController.java
  • 비즈니스 로직: src/plantpulse/app/edge/module/connect/ConnectService.java
  • 응답 envelope helper: plantpulse.app.edge.core.web.ControllerSupport (ok() / error())