
# 스마트 HTTP

Pitmaster은 smart HTTP 프로토콜을 사용해 HTTPS을 통해 git 서버와 통신합니다. 기본 네트워크 전송 방식입니다. HTTP 요청에는 PHP의 네이티브 스트림 함수(스트림 컨텍스트가 포함된 `fopen`)를 사용하므로 `allow_url_fopen`을 활성화해야 합니다.

## SmartHttpClient

```php
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);
```

시간 제한은 기본적으로 `PITMASTER_HTTP_TIMEOUT` 상수(정수)를 사용하며, 정의되지 않은 경우 30초입니다.

### ref 검색

```php
$discovery = $http->discoverRefs('https://github.com/user/repo.git');

foreach ($discovery->refs() as $name => $id) {
    echo "{$id->hex} {$name}\n";
}

// HEAD symref (e.g., 'refs/heads/main')
$defaultBranch = $discovery->headSymref();

// Specific ref
$mainId = $discovery->ref('refs/heads/main');

// Server capabilities (a Capability object, or null)
$caps = $discovery->capabilities();
```

`discoverReceivePackRefs()`은 `git-receive-pack`에도 같은 작업을 수행합니다. ref 검색 요청은 다음으로 전송됩니다:
```
GET <url>/info/refs?service=git-upload-pack
```

응답은 pkt-line으로 인코딩된 ref 목록이며, 첫 번째 줄에 기능이 포함됩니다.

### upload pack (fetch)

```php
$response = $http->uploadPack($url, $requestBody);
```

want/have 협상 본문과 함께 `<url>/git-upload-pack`으로 POST를 보냅니다. 원시 응답 본문(pkt-line, `NAK` 및 side-band 데이터)을 반환합니다. `UploadPackClient`가 요청을 구성하고 pack을 추출합니다:

```php
use Pitmaster\Protocol\UploadPackClient;

$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves);   // ObjectId[] wants and haves
```

### receive pack (push)

```php
$response = $http->receivePack($url, $requestBody);
```

ref 업데이트 명령과 pack 데이터를 포함해 `<url>/git-receive-pack`으로 POST를 보냅니다.

## PktLine 인코딩

git 프로토콜은 모든 통신에 pkt-line 프레이밍을 사용합니다. 각 줄 앞에는 총 길이(4바이트 길이 접두사를 포함)가 4자리 16진수로 표시됩니다.

```php
use Pitmaster\Protocol\PktLine;

// Encode a single line
$encoded = PktLine::encode("want abc123def456\n");
// "0016want abc123def456\n"

// Flush packet (end of section)
$flush = PktLine::flush();
// "0000"

// Delimiter packet (protocol v2)
$delim = PktLine::delimiter();
// "0001"

// Decode a stream
$lines = PktLine::decode($rawData);
// Each element is: string (data), null (flush), or false (delimiter)
```

### 길이 규칙

- 최소 데이터 줄: `0005` (페이로드 1바이트)
- 최대 데이터 줄: `fff0` (페이로드 65516바이트)
- Flush 패킷: `0000`
- 구분자 패킷: `0001`
- 응답 종료: `0002` (프로토콜 v2; `ProtocolV2::decode()`에서 처리하며 `PktLine::decode()`은 거부함)

```php
PktLine::MAX_PAYLOAD;  // 65516 (65520 - 4)
```

## ref 검색

`RefDiscovery` 클래스는 서버의 ref 광고를 파싱합니다.

```php
use Pitmaster\Protocol\RefDiscovery;

$discovery = RefDiscovery::parse($pktLines);

// All refs
$refs = $discovery->refs();
// ['refs/heads/main' => ObjectId, 'refs/tags/v1.0' => ObjectId, ...]

// Single ref
$id = $discovery->ref('refs/heads/main');

// HEAD symbolic ref (from capabilities)
$symref = $discovery->headSymref();
// 'refs/heads/main' or null

// Server capabilities
$caps = $discovery->capabilities();
// Capability object (see below), or null
```

### ref 광고 형식

광고의 첫 번째 줄에는 기능이 포함됩니다:

```
<hash> HEAD\0<capability list>\n
<hash> refs/heads/main\n
<hash> refs/tags/v1.0\n
0000
```

## 기능

`Capability` 클래스는 프로토콜 기능을 파싱하고 관리합니다.

```php
use Pitmaster\Protocol\Capability;

$caps = Capability::parse('thin-pack side-band-64k ofs-delta symref=HEAD:refs/heads/main');

$caps->has('thin-pack');           // true
$caps->get('symref');              // 'HEAD:refs/heads/main'
$caps->all();                      // ['thin-pack' => null, ..., 'symref' => 'HEAD:refs/heads/main']
```

### 일반 기능

| 기능 | 의미 |
|-----------|---------|
| `multi_ack` | 서버가 multi-ack 협상을 지원함 |
| `thin-pack` | 서버가 thin pack을 전송함(기본 객체가 생략될 수 있음). Pitmaster가 이를 요청하지만 아직 thin pack을 인덱싱할 수 없음([#16](https://github.com/inline0/pitmaster/issues/16)) |
| `side-band` | 서버가 pack 데이터에 side-band를 사용함(1000바이트로 제한) |
| `side-band-64k` | 서버가 pack 데이터에 64K side-band를 사용함 |
| `ofs-delta` | pack이 OFS_DELTA 객체를 사용할 수 있음 |
| `shallow` | 서버가 얕은 복제를 지원함 |
| `no-done` | 서버가 "done" 줄을 요구하지 않음 |
| `symref=HEAD:refs/heads/main` | HEAD의 심볼릭 대상 |
| `agent=git/2.45.0` | 서버 git 버전 |

## Side-band 역다중화

서버가 side-band를 사용하면 pack 데이터가 진행 상황 및 오류 메시지와 다중화됩니다. 각 pkt-line 페이로드의 첫 번째 바이트는 채널을 나타냅니다:

| 채널 | 의미 |
|---------|---------|
| 1 | Pack 데이터 |
| 2 | 진행 상황 메시지(stderr) |
| 3 | 치명적 오류 메시지 |

Pitmaster의 `UploadPackClient`은 채널 바이트를 제거하고 pack 데이터를 다시 조립합니다.

## 프로토콜 버전

### v1 (기본값)

기존 프로토콜입니다. ref 검색 시 모든 ref를 한 번에 전송합니다. Pitmaster은 단일 라운드로 협상합니다. 원하는 항목, 모든 로컬 ref tip을 `have`으로, 그리고 `done`를 전송합니다.

### v2

더 나은 성능을 제공하는 간단한 프로토콜입니다.

v1과 비교한 주요 차이점:
- ref 검색은 별도의 `ls-refs` 명령이며 ref를 필터링할 수 있음
- fetch는 구조화된 매개변수를 사용하는 단일 `fetch` 명령임
- 구분자 패킷(`0001`)을 사용해 섹션을 나눔

`clone()`과 `fetch()`은 항상 v1를 사용합니다. v2은 명시적으로 호출할 때 사용할 수 있습니다:

```php
$discovery = $http->discoverRefsV2($url);       // ls-refs
$packData = (new UploadPackClient($http))->fetchV2($url, $wants, $haves);
```

## HTTP 요청 흐름

### 복제/가져오기

```
1. GET  /info/refs?service=git-upload-pack
   Response: pkt-line encoded ref list

2. POST /git-upload-pack
   Request:  want <hash>\n ... have <hash>\n ... done\n
   Response: NAK\n + pack data (possibly side-band wrapped)
```

### 푸시

```
1. GET  /info/refs?service=git-receive-pack
   Response: pkt-line encoded ref list

2. POST /git-receive-pack
   Request:  <old-hash> <new-hash> refs/heads/main\0<capabilities>\n
             0000
             PACK<pack data>
   Response: unpack ok\n + ref status lines
```

## 오류 처리

네트워크 오류는 `ProtocolException`을 발생시킵니다. smart HTTP에서 `clone()` 또는 `fetch()` 교환이 실패하면 Pitmaster는 dumb HTTP으로 재시도하고, 이마저 실패하면 원래 오류를 다시 발생시킵니다. 알 수 없는 원격 저장소, 프로세스가 없는 핸들, 인덱싱할 수 없는 pack과 같은 다른 실패는 다른 예외 유형을 발생시킵니다:

```php
use Pitmaster\Exceptions\ProtocolException;

try {
    $repo->fetch();
} catch (ProtocolException $e) {
    echo "Network error: {$e->getMessage()}\n";
}
```

일반적인 오류:
- 서버에 연결할 수 없음(시간 초과)
- 인증이 필요하거나 저장소를 찾을 수 없음(`Unexpected HTTP status 401`, `403` 또는 `404`로 보고됨)
- 응답에 예상치 못한 `Content-Type`이 있음
- 잘못된 pkt-line 데이터
- side-band 오류 채널 메시지
