4646bool Trace_connection_negotiation = false;
4747uint32 log_connections = 0 ;
4848char * log_connections_string = NULL ;
49+ bool expose_recovery = false;
50+ bool expose_sysid = false;
51+ bool expose_version = false;
52+
4953
5054/* Other globals */
5155
@@ -65,6 +69,7 @@ static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
6569static void process_startup_packet_die (SIGNAL_ARGS );
6670static void StartupPacketTimeoutHandler (void );
6771static bool validate_log_connections_options (List * elemlist , uint32 * flags );
72+ static bool ExposeInformation (pgsocket fd );
6873
6974/*
7075 * Entry point for a new backend process.
@@ -148,6 +153,15 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
148153 StringInfoData ps_data ;
149154 MemoryContext oldcontext ;
150155
156+ /*
157+ * Scan for a simple GET / HEAD request. If this is detected and
158+ * handled, we are done and can immediately exit
159+ */
160+ if ((expose_recovery || expose_sysid || expose_version )
161+ && ExposeInformation (client_sock -> sock ))
162+ proc_exit (0 );
163+ /* Should we do exit(0) here, despite the warnings in ipc.c? */
164+
151165 /* Tell fd.c about the long-lived FD associated with the client_sock */
152166 ReserveExternalFD ();
153167
@@ -1126,3 +1140,180 @@ assign_log_connections(const char *newval, void *extra)
11261140{
11271141 log_connections = * ((int * ) extra );
11281142}
1143+
1144+
1145+ static
1146+ bool
1147+ ExposeInformation (pgsocket fd )
1148+ {
1149+
1150+ /*
1151+ * ExposeInformation
1152+ *
1153+ *
1154+ * Handle early socket probe before full backend startup.
1155+ * Responds to small set of predefined endpoints (e.g. GET /info)
1156+ *
1157+ * Requires at least one "expose_" GUC to be true.
1158+ *
1159+ * Returns true if any endpoint is recognized.
1160+ */
1161+
1162+ #define EXPOSE_MIN_QUERY 9 /* Shortest possible line: "Get /info" */
1163+ #define EXPOSE_MAX_QUERY 16 /* Longest possible GET line */
1164+
1165+ /* What information is being returned */
1166+ typedef enum
1167+ {
1168+ EXPOSE_NOTHING ,
1169+ EXPOSE_HEAD_REPLICA ,
1170+ EXPOSE_GET_ALL ,
1171+ EXPOSE_GET_REPLICA ,
1172+ EXPOSE_GET_SYSID ,
1173+ EXPOSE_GET_VERSION ,
1174+ } ReturnType ;
1175+
1176+ typedef struct
1177+ {
1178+ const char * endpoint ;
1179+ const bool * require ;
1180+ ReturnType type ;
1181+ } endpoint_action ;
1182+
1183+ static endpoint_action endpoint_actions [] =
1184+ {
1185+ {
1186+ "HEAD /replica" , & expose_recovery , EXPOSE_HEAD_REPLICA
1187+ },
1188+ {
1189+ "GET /replica" , & expose_recovery , EXPOSE_GET_REPLICA
1190+ },
1191+ {
1192+ "GET /sysid" , & expose_sysid , EXPOSE_GET_SYSID
1193+ },
1194+ {
1195+ "GET /version" , & expose_version , EXPOSE_GET_VERSION
1196+ },
1197+ {
1198+ "GET /info" , NULL , EXPOSE_GET_ALL
1199+ }
1200+ };
1201+
1202+ ssize_t n ;
1203+ char buf [EXPOSE_MAX_QUERY + 1 ];
1204+ int type ;
1205+
1206+ Assert (expose_recovery || expose_sysid || expose_version );
1207+
1208+ do
1209+ {
1210+ n = recv (fd , buf , EXPOSE_MAX_QUERY , MSG_PEEK );
1211+ } while (n < 0 && errno == EINTR );
1212+
1213+ /*
1214+ * Leave as soon as possible if no chance we are interested. We also
1215+ * simply return false for n == -1
1216+ */
1217+ if (n < EXPOSE_MIN_QUERY )
1218+ return false;
1219+
1220+ buf [n ] = '\0' ;
1221+
1222+ type = EXPOSE_NOTHING ;
1223+ for (int i = 0 ; i < lengthof (endpoint_actions ); i ++ )
1224+ {
1225+ if (
1226+ strncmp (buf , endpoint_actions [i ].endpoint , strlen (endpoint_actions [i ].endpoint )) == 0
1227+ &&
1228+ (endpoint_actions [i ].require == NULL
1229+ ||
1230+ * (endpoint_actions [i ].require )
1231+ ))
1232+ {
1233+ type = endpoint_actions [i ].type ;
1234+ break ;
1235+ }
1236+ }
1237+
1238+ if (type == EXPOSE_NOTHING )
1239+ return false;
1240+
1241+ {
1242+ static const char http_version [] = "HTTP/1.1" ;
1243+ static const char http_type [] = "Content-Type: text/plain" ;
1244+ static const char * http_conn = "Connection: close" ;
1245+ static const char http_len [] = "Content-Length" ;
1246+
1247+ StringInfoData msg ;
1248+
1249+ if (type == EXPOSE_HEAD_REPLICA )
1250+ {
1251+ /*
1252+ * Caller only cares about the HTTP response code, so no content
1253+ * needed
1254+ */
1255+
1256+ initStringInfoExt (& msg , 64 );
1257+
1258+ appendStringInfo (& msg ,
1259+ "%s %s\r\n"
1260+ "%s\r\n"
1261+ "%s\r\n\r\n" ,
1262+ http_version ,
1263+ (RecoveryInProgress () ? "200 OK" : "503 Service Unavailable" ),
1264+ http_type ,
1265+ http_conn
1266+ );
1267+ }
1268+ else
1269+ {
1270+ StringInfoData content ;
1271+
1272+ initStringInfoExt (& content , 64 );
1273+
1274+ if (expose_recovery && (type == EXPOSE_GET_ALL || type == EXPOSE_GET_REPLICA ))
1275+ appendStringInfo (& content , "%s%d\r\n" ,
1276+ type == EXPOSE_GET_ALL ? "RECOVERY: " : "" ,
1277+ RecoveryInProgress () ? 1 : 0 );
1278+ if (expose_sysid && (type == EXPOSE_GET_ALL || type == EXPOSE_GET_SYSID ))
1279+ appendStringInfo (& content , "%s%lu\r\n" ,
1280+ type == EXPOSE_GET_ALL ? "SYSID: " : "" ,
1281+ GetSystemIdentifier ());
1282+ if (expose_version && (type == EXPOSE_GET_ALL || type == EXPOSE_GET_VERSION ))
1283+ appendStringInfo (& content , "%s%d\r\n" ,
1284+ type == EXPOSE_GET_ALL ? "VERSION: " : "" ,
1285+ PG_VERSION_NUM );
1286+
1287+ initStringInfoExt (& msg , 256 );
1288+
1289+ appendStringInfo (& msg ,
1290+ "%s 200 OK\r\n"
1291+ "%s\r\n"
1292+ "%s: %d\r\n"
1293+ "%s\r\n\r\n"
1294+ "%s" ,
1295+ http_version ,
1296+ http_type ,
1297+ http_len , content .len ,
1298+ http_conn ,
1299+ content .data
1300+ );
1301+
1302+ pfree (content .data );
1303+ }
1304+
1305+ do
1306+ {
1307+ n = send (fd , msg .data , msg .len , 0 );
1308+ } while (n < 0 && errno == EINTR );
1309+
1310+ pfree (msg .data );
1311+
1312+ if (n < 0 )
1313+ elog (DEBUG1 , "could not send to client: %m" );
1314+
1315+ return true;
1316+
1317+ }
1318+
1319+ }
0 commit comments