Skip to content
1 change: 1 addition & 0 deletions .noindex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 1 addition & 1 deletion src/Commands/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function setContext(): void
$context = app(Context::class);
$meta = new Metadata();
$meta->task_id = $this->taskID;
$meta->identifier = $this->signature;
$meta->identifier = explode(" ", $this->signature)[0];
$context->set(Metadata::class, $meta);
}
}
31 changes: 30 additions & 1 deletion src/Libraries/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function __construct(array $config = [])
* @var Metadata $meta
*/
$meta = $context->get(Metadata::class);
if (!is_null($meta)) {
if ($meta !== null) {
if (isset($meta->user_agent) && $meta->user_agent !== null) {
$this->requestHeaders['User-Agent'] = $meta->user_agent;
}
Expand All @@ -101,6 +101,8 @@ public function __construct(array $config = [])
}
if (isset($meta->req_id) && $meta->req_id !== null) {
$this->requestHeaders['X-Request-ID'] = $meta->req_id;
} elseif (isset($meta->task_id) && $meta->task_id !== null) {
$this->requestHeaders['X-Request-ID'] = $meta->task_id;
}
if (isset($meta->req_user) && $meta->req_user !== null) {
$this->requestHeaders['X-Request-User'] = $meta->req_user;
Expand Down Expand Up @@ -141,6 +143,33 @@ public function __construct(array $config = [])
if (isset($meta->req_role) && $meta->req_role !== null) {
$this->requestHeaders['X-Request-Role'] = $meta->req_role;
}
if (isset($meta->req_uker_supervised) && $meta->req_uker_supervised !== null) {
$this->requestHeaders['X-Request-Uker-Supervised'] = json_encode($meta->req_uker_supervised);
}
if (isset($meta->req_stell) && $meta->req_stell !== null) {
$this->requestHeaders['X-Request-Kode-Org-Jabatan'] = $meta->req_stell;
}
if (isset($meta->req_stell_tx) && $meta->req_stell_tx !== null) {
$this->requestHeaders['X-Request-Nama-Org-Jabatan'] = $meta->req_stell_tx;
}
if (isset($meta->req_kostl) && $meta->req_kostl !== null) {
$this->requestHeaders['X-Request-Kode-Cost-Center'] = $meta->req_kostl;
}
if (isset($meta->req_kostl_tx) && $meta->req_kostl_tx !== null) {
$this->requestHeaders['X-Request-Nama-Cost-Center'] = $meta->req_kostl_tx;
}
if (isset($meta->req_orgeh) && $meta->req_orgeh !== null) {
$this->requestHeaders['X-Request-Kode-Org-Unit'] = $meta->req_orgeh;
}
if (isset($meta->req_orgeh_tx) && $meta->req_orgeh_tx !== null) {
$this->requestHeaders['X-Request-Nama-Org-Unit'] = $meta->req_orgeh_tx;
}
if (isset($meta->req_level_uker) && $meta->req_level_uker !== null) {
$this->requestHeaders['X-Request-Level-Uker'] = $meta->req_level_uker;
}
if (isset($meta->req_uid) && $meta->req_uid !== null) {
$this->requestHeaders['X-Request-Uid-Las'] = $meta->req_uid;
}
}
}

Expand Down
155 changes: 138 additions & 17 deletions src/Libraries/ClientExternal.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Illuminate\Support\Facades\Redis;
use Psr\Http\Message\ResponseInterface;
use Spotlibs\PhpLib\Exceptions\InvalidRuleException;
use Spotlibs\PhpLib\Libraries\MapRoute;
use Spotlibs\PhpLib\Logs\Log;
use Spotlibs\PhpLib\Services\Context;
use Spotlibs\PhpLib\Services\Metadata;
Expand Down Expand Up @@ -160,15 +159,26 @@ public function call(Request $request, array $options = []): ResponseInterface
}
$elapsed = microtime(true) - $startime;

// Parse request body
$request->getBody()->rewind();
$reqbody = $request->getBody()->getContents();
$respbody = $response->getBody()->getContents();
if (strlen($reqbody) > 5000) {
$reqbody = "more than 5000 characters";
$reqBody = $request->getBody()->getContents();
$detectedType = $this->detectContentType($reqBody);
if ($detectedType === 'multipart/form-data') {
$parsedReqBody = $this->parseBody($reqBody);
} elseif (strlen($reqBody) > 5000) {
$parsedReqBody = "more than 5000 characters";
} else {
$parsedReqBody = $this->parseBody($reqBody);
}
if (strlen($respbody) > 5000) {
$respbody = "more than 5000 characters";

// Parse response body
$respBody = $response->getBody()->getContents();
if (strlen($respBody) > 5000) {
$parsedRespBody = "more than 5000 characters";
} else {
$parsedRespBody = $this->parseBody($respBody);
}

$logData = [
'app_name' => env('APP_NAME'),
'path' => is_null($metadata) ? null : $metadata->identifier,
Expand All @@ -177,29 +187,140 @@ public function call(Request $request, array $options = []): ResponseInterface
'request' => [
'method' => $request->getMethod(),
'headers' => $request->getHeaders(),
'body' => $parsedReqBody
],
'response' => [
'httpCode' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'body' => $parsedRespBody
],
'responseTime' => round($elapsed * 1000),
'memoryUsage' => memory_get_usage()
];
if ($request->getHeader('Content-Type') == ['application/json']) {
$logData['request']['body'] = json_decode($reqbody, true);
} else {
$logData['request']['body'] = $reqbody;
}
if ($response->getHeader('Content-Type') == ['application/json']) {
$logData['response']['body'] = json_decode($respbody, true);
} else {
$logData['response']['body'] = $respbody;
}

$response->getBody()->rewind();
Log::activity()->info($logData);
return $response;
}

/**
* Detect Content Type
*
* @param string $body Raw body content
*
* @return string
*/
private function detectContentType(string $body): string
{
if (empty($body)) {
return 'empty';
}

// Check for multipart - look for Content-Disposition
if (preg_match('/Content-Disposition:\s*form-data/i', $body)) {
return 'multipart/form-data';
}

// Check for URL-encoded - look for key=value pattern
if (preg_match('/^[^=]+=/', $body) && !str_contains($body, "\n")) {
return 'application/x-www-form-urlencoded';
}

// Check for JSON - try to decode
if ($body[0] === '{' || $body[0] === '[') {
json_decode($body);
if (json_last_error() === JSON_ERROR_NONE) {
return 'application/json';
}
}

return 'text/plain';
}

/**
* Parse body content based on content type
*
* @param string $body Raw body content
*
* @return mixed
*/
private function parseBody(string $body): mixed
{
if (empty($body)) {
return null;
}

$detectedType = $this->detectContentType($body);

if ($detectedType === 'application/json') {
return json_decode($body, true) ?? $body;
}

if ($detectedType === 'multipart/form-data') {
return $this->parseMultipartFormDataFromBody($body);
}

if ($detectedType === 'application/x-www-form-urlencoded') {
parse_str($body, $parsed);
return $parsed;
}

return $body;
}

/**
* Parse multipart form data into readable array
*
* @param string $body Raw body content
*
* @return array
*/
private function parseMultipartFormDataFromBody(string $body): array
{
$parsed = [];

// Extract boundary from first line
preg_match('/^--([^\r\n]+)/', $body, $boundaryMatch);
if (empty($boundaryMatch[1])) {
return ['raw' => 'Unable to parse multipart'];
}

$boundary = $boundaryMatch[1];
$parts = explode("--$boundary", $body);

foreach ($parts as $part) {
if (trim($part) === '' || trim($part) === '--') {
continue;
}

// Split headers and content
$sections = preg_split('/\r?\n\r?\n/', $part, 2);
if (count($sections) < 2) {
continue;
}

$headers = $sections[0];
$content = trim($sections[1]);

// Extract field name
if (preg_match('/name="([^"]+)"/', $headers, $nameMatch)) {
$name = $nameMatch[1];

// Check if it's a file
if (preg_match('/filename="([^"]+)"/', $headers, $fileMatch)) {
$parsed[$name] = [
'filename' => $fileMatch[1],
'contents' => "[BINARY FILE DATA - " . strlen($content) . " bytes]"
];
} else {
$parsed[$name] = $content;
}
}
}

return $parsed;
}

/**
* Check if url shall mock
*
Expand Down
9 changes: 9 additions & 0 deletions src/Middlewares/ActivityMonitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ public function handle($request, Closure $next)
$meta->version_app = $request->header('X-Version-App');
$meta->identifier = $request->getPathInfo();
$meta->req_role = $request->header('X-Request-Role');
$meta->req_uker_supervised = json_decode($request->header('X-Request-Uker-Supervised'));
$meta->req_stell = $request->header('X-Request-Kode-Org-Jabatan');
$meta->req_stell_tx = $request->header('X-Request-Nama-Org-Jabatan');
$meta->req_kostl = $request->header('X-Request-Kode-Org-Cost-Center');
$meta->req_kostl_tx = $request->header('X-Request-Nama-Org-Cost-Center');
$meta->req_orgeh = $request->header('X-Request-Kode-Org-Unit');
$meta->req_orgeh_tx = $request->header('X-Request-Nama-Org-Unit');
$meta->req_level_uker = $request->header('X-Request-Level-Uker');
$meta->req_uid = $request->header('X-Request-Uid-Las');
$this->contextService->set(Metadata::class, $meta);

$this->contextService->set('method', $request->method());
Expand Down
9 changes: 9 additions & 0 deletions src/Services/Metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ class Metadata
public ?string $path_gateway;
public ?string $identifier;
public ?string $req_role;
public ?array $req_uker_supervised;
public ?string $req_stell;
public ?string $req_stell_tx;
public ?string $req_kostl;
public ?string $req_kostl_tx;
public ?string $req_orgeh;
public ?string $req_orgeh_tx;
public ?string $req_level_uker;
public ?string $req_uid;
}
2 changes: 2 additions & 0 deletions tests/Dtos/DtosTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ public function testDtoWithAliases(): void
$this->assertIsArray($y['partner']['dog']);
$this->assertEquals('Joshua', $y['partner']['dog']['name']);
$this->assertEquals('Jacob', $x->siblings[0]->name);
$this->assertArrayNotHasKey('arrayOfObjectMap', $y);
$this->assertArrayNotHasKey('aliases', $y);
}
/** @test */
/** @runInSeparateProcess */
Expand Down
24 changes: 23 additions & 1 deletion tests/Libraries/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ public function testCallY(): void
$meta->req_nama_uker = 'test_name';
$meta->path_gateway = 'test_path';
$meta->identifier = 'test_identifier';
$meta->req_uker_supervised = ['abc', 'def'];
$meta->req_stell = '123';
$meta->req_stell_tx = 'abc';
$meta->req_kostl = '123';
$meta->req_kostl_tx = 'abc';
$meta->req_orgeh = '123';
$meta->req_orgeh_tx = 'abc';
$meta->req_level_uker = 'X';
$meta->req_uid = 'abc123';
$meta->req_role = 'abc';
/**
* @var \Mockery\MockInterface $context
*/
Expand Down Expand Up @@ -80,6 +90,14 @@ public function testCallX(): void
'GET',
'https://dummyjson.com/test',
);
$meta = new Metadata();
$meta->task_id = 'abcd';
/**
* @var \Mockery\MockInterface $context
*/
$context = Mockery::mock(Context::class);
$context->shouldReceive('get')->with(Metadata::class)->andReturn($meta);
$this->app->instance(Context::class, $context);
$client = new Client(['handler' => $handlerStack]);
$response = $client->call($request);
$contents = $response->getBody()->getContents();
Expand All @@ -101,7 +119,11 @@ public function testCallZ(): void
"message" => "welcome"
])
);
$client = new Client();
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], json_encode(['status' => 'ok', 'message' => 'well done'])),
]);
$handlerStack = new HandlerStack($mock);
$client = new Client(['handler' => $handlerStack]);
$response = $client
->injectRequestHeader(['X-Powered-By' => ['Money']])
->injectResponseHeader(['X-Server' => ['tinyurl'], 'X-Overhead' => ['true', 'allowed']])
Expand Down